线程的行为。
核心对象的概念,线程和线程对象的概念。
建立一个线程,释放线程,监听线程,结束线程的win32方法。
一、 API: CreateThread,创建一个线程
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // pointer to security attributes
DWORD dwStackSize, // initial thread stack size
LPTHREAD_START_ROUTINE lpStartAddress, // pointer to thread function
LPVOID lpParameter, // argument for new thread
DWORD dwCreationFlags, // creation flags
LPDWORD lpThreadId // pointer to receive thread ID
);
1. 一些说明
1) 返回值可以用来判断创建是否成功,成功时返回值是Thread的handle,可以用来指向该线程,从后面看,获取线程返回值需要该handle。
2) 一般调用方式( NULL,0,函数名,(LPVOID)函数参数,0,&ThreadId )
a) Security属性默认,stackSize为0则大小默认为一兆;
b) 启动方式0为“立即开始执行”,我认为是与“挂起”对立的概念,后面的执行中看到,也并非能保证线程真的立即执行。
c) 大部分API函数用handle, ThreadId有时候有用,比如两个影响其他线程消息的函数。
3) 线程函数
a) 名字任意
b) 返回值是DWORD,调用约定是WINAPI,参数是LPVOID
c) 该参数可以是一个void型指针,也可以直接当Int用。
二、 多线程的行为
1. 执行具体过程受多方面因素影响,无法预期,也无法重现。【所以某次的失败,之后也无法重现他是怎么失败的】
2. 线程之间的执行次序应该认为是随机的,并不是先启动的就先执行。
3. task switches可能在任何时间发生
1) 书上例子没有用 C runtime library,于是出线了printf执行到一半被打断的情形
2) 用vc6调试的结果,没有出线打断情况,但launch输出行和数字行都有被打多的情况。【具体原因不明,大概是由C runtime library引起】
4. 线程的实际执行情况,对程序上的小改变,高度敏感。
5. 虽然dwCreationFlag是0,但是线程并不是立即启动的【稍后讨论?】
三、 API:CloseHandle与核心对象
BOOL CloseHandle(
HANDLE hObject // handle to object to close
);
1. 核心对象的概念
1) 和GDI对象一样,不需要知道其数据结构,只需要得到其handle,通过调用API函数使用
2) 与GDI对象不同,只有一种handle——HANDLE;且可以有多个拥有者。
3) 包括process, thread, files, events, semaphores, mutexes, pipes
2. 最出乎意料的概念:
线程与线程对象( Thread and Thread object )不是一个概念,也不是一个东东。
线程对象与该对应的线程有一种引用关系,再加上调用CreateThread并拥有该线程对象的线程,一个线程对象初始默认的计数器为2.
拥有线程CloseHandle会导致计数器-1,所引用线程结束再导致计数器-1,该线程计数器才会降为0.
3. (结合第三章)关于线程的结束和激活:
线程结束,return或ExitThread,线程不在了,但线程对象还在,其线程索引计数器-1,但还不为零。线程结束导致线程对象成为激活状态,在wait该线程对象的线程被唤醒
4. reference count和清扫(clean up)
1) 每个核心对象可以有多个拥有者,所以要计数。【并记录都有谁拥有他】
2) 线程或进程结束时会clean up,其动作包括对所拥有对象计数器-1.
3) 根据核心对象的性质不同,对象的拥有者可以是进程或线程,则clean up的时机不同。
5. 虽然clean up最后也会清扫,但很【有必要手动close handle】
1) 核心对象由多个进程拥有时,释放顺序可能是重要的【想象不出…】
2) 某线程可能拥有很多对象,不释放是一种内存泄露。
3) 由于某些对象由进程拥有,线程不释放的话,等clean up释放会很滞后,产生内存泄露问题。
6. 引用计数机制产生的奇妙情况
1) 一个核心对象可被多个线程或进程拥有,所以可能出线创造者作古,而核心对象继续为其他进程或线程使用的情况。
2) 可能会有一个新的线程来调用CloseHandle(), 来取代之前的线程。【未完全明白。】
四、 API:GetExitCodeThread() 获得线程函数的返回值、也可用来监听线程(busy wait)
BOOL GetExitCodeThread(
HANDLE hThread, // handle to the thread
LPDWORD lpExitCode // address to receive termination status
);
1. 返回值不表示线程结束,只表示函数调用成功。
2. 两个参数,一个参数为handle,一个表示返回值或STILL_ACTIVE
3. 就是说handle给主线程一个机会获得新线程的返回值。
4. 可以反复调用该函数测试分线程是否结束,但会形成busy wait。
五、 API:ExitThread 终结一个线程
VOID ExitThread(
DWORD dwExitCode // exit code for this thread
);
1. 该函数强制终结本线程,而不是终结其他线程。
2. 如果某函数调用了这个API函数,则那个函数不会返回,那个函数调用之后的代码也不会被执行。
3. 如果某线程中出线了上述情况,该线程的返回不会被执行到,GetExitCodeThread得到的返回值由ExitThread提供
4. 不建议在主线程中使用该函数。
六、 主线程、结束主线程
1. 两个特点:
1) 必须负责GUI程序中的主消息循环
2) 主线程中使用ExitThread,其他work线程依然存在,可以编程检验。参见MSDN: ExitThread。
3) 主线程中使用ExitThread在书中也强调会强制终止分线程,使分线程没有机会clean up,从而造成工作未完成和泄露。【我猜测,可能MFC创建的GUI程序会有这种机制】
2. 很多不确定的,确定的如下:
1) 尽量不要在主线程中使用ExitThread。
2) 主线程结束前应等待其他线程结束。【见第三章:在消息循环中加入线程计数判断】
七、 MTVERIFY适用于gui和console程序
八、 后台打印线程实例:一些注意点,和铺垫
1. win32中线程分为GUI线程和worker线程。Worker线程不应该创建窗口,因为创建了就需要管理消息循环。Worker线程的任何输出,甚至报错,都应该授权GUI线程来做。
2. 简单、安全更重要于复杂、速度;表面积尽量少——准备好数据,打包之后再开始线程。
3. 全局变量容易被改写,局部变量容易过生存期,heap最安全。主线程中申请,分线程中释放。
第二章,一些基本的w32线程操作
最新推荐文章于 2019-04-03 22:47:14 发布