《Win32多线程程序设计》线程初识

来源:互联网 发布:php走势图源码 编辑:程序博客网 时间:2024/06/05 16:25

进程

就是一大堆对象的拥有权的集合。【这解释言简意赅,我喜欢】

多线程vs多进程

多线程:线程价廉。线程启动比较快,退出比较快,对系统资源的冲击也比较小。而且, 线程彼此分享了大部分核心对象(如 file handles)的拥有权。
多重进程:最困难的问题大概是如何把窗口的 handle 交给另一个进程。为了分享窗口 handle,你必须明明白白地产生该 handle 的一个副本,并且可以被其他进程使用。在一
个多线程程序中,所有线程都可以使用这个窗口 handle,因为 handle 和线程生活在同一个进程之中。

context switch

当硬件计时器认为某个线程已经执行够久了,就会发出一个中断( interrupt),于是 CPU 取得目前这个线程的当前状态,也就是把所有寄存器内容拷贝到堆栈之中,再把它从堆栈拷贝到一个 CONTEXT 结构 (这样便储存了线程的状态)中,以备以后再用。
要切换不同的线程,操作系统应先切换该线程所隶属之进程的内存,然后恢复该线程放在 CONTEXT 结构中的寄存器值。这整个过程便称为context switch。

CPU 愈多,就有愈多线程可以同时执行,不需要 context switch

不同进程间的线程如果要通讯,唯有依赖特别的设计,使之拥有共享内存(shared memory)。如果两个线程属于同一进程,它们将共享所有的内存。

CreateThread

HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,DWORD dwStackSize,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,DWORD dwCreationFlags,LPDWORD lpThreadId);

参数
lpThreadAttributes 描述施行于这一新线程的 security 属性。NULL 表示使用缺省值。此参数在 Windows 95 中被忽略。
dwStackSize 新线程拥有自己的堆栈。0 表示使用缺省大小:1MB。
lpStartAddress 新线程将开始的起始地址。这是一个函数指针。(译注:在 C 语言中函数名称即代表函数指针,所以这
里可放一个函数名称)
格式要求:返回值为 DWORD, 调用约定是 WINAPI (译注), 有一个 LPVOID参数。
DWORD WINAPI ThreadFunc(LPVOID n)
lpParameter 此值将被传送到上述所指定之新线程函数去,作为参数。
dwCreationFlags 允许你产生一个暂时挂起的线程。默认情况是“立即开始执行”。
lpThreadId 新线程的 ID 会被传回到这里。
线程 ID 是一个全局变量,可以独一无二地表示系统任一进程中的某个线程。AttachThreadInput()PostThreadMessage()就需要用到线程 ID,这两个函数允许你影响其他人(线程)的消息队列。调试器和进程观察器也需要线程 ID。为了安全防护的缘故,你不可能根据一个线程的 ID 而获得其 handle。

返回值:
如果 CreateThread( ) 成功,传回一个 handle,代表新线程。否则传回一个FALSE。如果失败,你可以调用 GetLastError( )获知原因。

此handle称为一个核心对象(kernel object),类似GDI对象,如画笔、画刷或DC,只不过它由 KERNEL32.DLL 管理,而非 GDI32.DLL 管理。 属进程所有,而非线程所有。

所谓handle,其实是个指针,指向操作系统内存空间中的某样东西,那东西不允许你直接取得。你的程序不能够直接取用它,为的是维护系统的完整性与安全性。

Win32 核心对象:
- 进程(processes)
- 线程(threads)
- 文件(files)
- 事件(events)
- 信号量(semaphores)
- 互斥器(mutexes)
- 管道(Pipes。分为 named 和 anonymous 两种)

GDI 对象和核心对象之间有一个主要的不同。GDI 对象有单一拥有者,不是进程就是线程。核心对象可以有一个以上的拥有者,甚至可以跨进程。核心对象保持了一个引用计数(reference count),以记录有多少 handles 对应到此对象。

重导至文件的那些输出远比屏幕上的输出快得多。

多线程特点:
不总是立即启动,
执行次序无保证,
task switches可能在任何时刻任何地点发生(汇编)

CloseHandle 的重要性

BOOL CloseHandle (HANDLE hObject);

成功返回TRUE,否则返回FALSE.
若进程没有在结束之前对他所打开的核心对象调用closehandle(),操作系统会自动将引用计数降1。但最好不要这样做。
你不可以依赖“因线程的结束而清理所有被这一线程产生的核心对象”。许多对象,例如文件,是被进程拥有,而非被线程拥有。在进程结束之前不能够清理它们。

为什么可以在不结束线程的情况下关闭其handle?
“线程核心对象”引用到的那个线程也会令核心对象开启。因此,线程对
象的默认引用计数是 2。当你调用 CloseHandle( )时,引用计数下降 1,当线
程结束时,引用计数再降 1。只有当两件事情都发生了(不管顺序如何)的时
候,这个对象才会被真正清除。

GetExitCodeThread

BOOL GetExitCodeThread(HANDLE hThread,LPDWORD lpExitCode);

成功返回TRUE,否则返回FALSE.
若线程已结束,lpExitCode 参数为结束码。若线程尚未结束,lpExitCode 带回来的值是 STILL_ACTIVE。
使用 GetExitCodeThread( ) 等待一个线程的结束,这并不是好方法。GetExitCodeThread( )的一个糟糕行为是,当线程还在进行,尚未有所谓结束代码时,它会传回 TRUE 表示成功。如果这样,lpExitCode 指向的内存区域中应该放的是 STILL_ACTIVE。你必须小心这种行为,也就是说你不可能从其返回值中知道“到底是线程还在运行呢,还是它已结束,但返回值为STILL_ACTIVE”。

ExitThread
VOID ExitThread(DWORD dwExitCode);

任何代码若放在此行之下,保证不会被执行。

多线程程序设计的成功关键:
1. 各线程的数据要分离开来,避免使用全局变量。
2. 不要在线程之间共享 GDI 对象。
3. 确定你知道你的线程状态。不要径自结束程序而不等待它们的结束。
4. 让主线程处理用户界面(UI)。

阅读全文
1 1
原创粉丝点击