人们吃饭的时候总是这样的:一边吃一边喝一边呼吸。如果我是创建人类的大自然,我肯定会选择抢先式多任务。
如果盖房,这需要打地基、和水泥、砌墙,那就得找你个帮手了。作为主线程的我就叫了几个帮手做我的子线程直到把房子盖好。
二、线程和进程有什么不同?
在Windows中,进程是对象和句柄的巨大熔池,饱含所有资源却什么都不会做。而在UNIX中,内核黑客们为进程绑了一个主线程,即进程除了是资源集合外,天生是可以运行的。
线程则是这巨大池子中的鱼,往返穿梭、自由且忙碌,呼吸着、左右着这些资源。
三、Context switch(上下文转换)的发生。
对同一对象操作是发生context switch是有风险的, 我们称之为race condition 竞争条件。
四、Atomic Operations 原子操作应运而生。
用标志位来隔离多线程也是不科学的,会生产N多行代码,发生context switch机会很大。原子性应发生在机器码—汇编的级别上。所以我们需要一个不被操作系统中断的标志位操作。
五、线程间如何通信?
让很多人同时在厨房帮你炒几道菜,通常不是很顺利。让多线程同时处理同一件事情值得我们小心谨慎。
六、HANDLE内核对象需要释放。
七、线程内核对象与线程的不同?
八、多线程的成功:
保证与主线程有最小的接触面积。
各线程间的数据要相互分离,避免使用全局变量,避免共同使用GDI对象。
让主线程处理UI界面。
九、线程的等待:不要在Win32中使用GetExitThread() ,会对系统资源造成严重的冲击。
我们可以用waitSingleObject替代。这两个函数都在线程核心对象被激发时返回。阻塞等待比每秒访问几百万次某个函数要好得多。
十、什么是一个被激发的对象?当线程运行时,线程核心对象未激发;当线程结束时,线程核心对象被激发。
十一、 GetExitCodeThread()获得一个也终止线程的ID
十二、 waitForMultipleObject();同时等待多个监控多个内核对象的激发。
十三、 关于内核对象:
进程在被初始化时,系统为其分配一个句柄表,此句柄表只用于内核对象。 句柄表的每一项记录了每个内核对象的信息,所谓信息主要包括:(句柄在句柄表表中的)索引、指向内核对象内存的地址、访问屏蔽标志、内核对象句柄继承标志。
进程在初始化时句柄表是空的,每当进程创建一个内核对象时,系统就在该进程的句柄
表中找出一个空项,设置新创建的内核对象的信息。 内核对象的创建函数几乎都是形如Cerate*的形式,这些函数返回新创建的内核对象的句柄。这里返回的所谓句柄,其值实际上是该内核对象在进程句柄表中的索引,用于标识该对象在进程句柄表中的位置。
Cerate*函数返回失败的话,返回值通常为NULL(0),但也有一些是INVALID_HANDLE_VALUE(-1)。关闭内核对象通过调用CloseHandle完成,每调用一次,内核对象使用计数递减1。
十四、 msgWaitForMultipleObjects();等待内核对象或消息的激发。
十五、 不要长期锁定一个资源 。
十六、 在critical section(临界区) 中如果线程down掉,我们无法取消这个section,如果需要这种机制可使用mutex,我们在监视线程退出时可以进行取消。
十七、 死锁。哲学家问题,应用Mutex可以解决。
十八、 Mutex(互斥器)上锁是对内核对象进行操作,花费时间要比critical section长,
可以跨进程使用。
可指定等待时间。
十九、 哲学家问题,用WaitForMultipleObjects()可同时等待多只筷子的到来。
二十、 Semaphore(信号量)可以针对一组相同个体资源,并设立一个最大计数。当计数用完之后,之后的线程就必须等待。可以具名,可被多进程访问。
二十一、 信号量(semaphore)和互斥器(Mutex)在刚建立时都要将设法不要让别的线程立即使用。如:信号量里的create时设数量为0,Mutex中设当前线程占有Mutex。
二十二、 注意EVENT的遗失现象。Event主要用于同步,是核心对象,他的激活它是可控制的,比较方便,多用于IOCP。可具名,被其他进程访问
第二节 新的线程控制
一、在一个线程中结束另一个线程。
TerminateThread(),会引起内存泄漏。比较危险。
二、Signal(信号):
三、优先级:GetPriorityClass() / SetPriorityClass(); 微调:GetThreadPriority()、SetThreadPriority()
第三节 overlap IO
一、可以同时读写文件的许多部分。
二、OVERLAPPED结构体保存了信息。
三、以文件HANDLE作为激发机制,可用waitForSingleObject()/ GetoverlappedResult()来等待未完的异步事件完成。别切在OVERLAPPED中保存了所需要的信息。
四、在OVERLAPPED的末尾设置EVENT结构体,当异步事件完成时,操作系统可以激发次EVENT核心对象。并且采用手动,不让操作系统自动激发它,导致竞争条件。
从而可实现在等待同一个文件的多个事件—好像文件被切割后用事件通知一样。
五、重叠IO的限制如下:最多同时等待64个事件。在传输小于32k的数据是平均要比普通方法多花费15%的时间。
六、IOCP:极大的发掘CPU的速率,对多CPU的服务器很适合。
七、IOCP对线程数的控制。有时可能因为客观需要实际线程数大于CPU上最大线程数。
八、IOCP中如果执行线程因为某事(如文件io)而挂起,则CPu将闲置,故应创建比CPU数多的线程数。
创建线程数 = 执行中线程数 + 被阻塞线程数 + CP上等待着的后备线程数
九、避免返回Completion Packets。不需要IOCP返回一个包。创建OVERLAPPED结构体,初始化一个manul-reset 手工重置的EVENT结构体,放在OVERLAPPED中,并将最后一位置1.------《WIN32多线程程序设计》P182。
十、因为c/c++运行标准库比多线程出现的早,所以直接用CreateThread()会有同步方面的错误。因此,高手们给我们提供了beginThreadEx()来解决这个问题。
十一、 排它锁:在一个就结构体内部设立一个锁变量,任何线程访问时都要开锁,迫使多线程的同步访问变成顺序访问,带来了安全,却失去了多线程的优势。
十二、 C++的_beginThreadEx()中的参数中要使用unsigned类型,可定义一个typeDefine来转换。
十三、 用C++的成员函数实现线程时,相应函数因为默认需要一个this指针,但操作系统不知道,扔了一个LRARAM进去。
解决的办法,将相应成员函数设为static。并将this作为参数。
十四、 C++类中添加Critical Section。可用C++对Critical Section对象进行封装。
十五、 C++错误处理,
十六、 MFC中以挂起状态启动线程。CWindThread(),AfxBeginThread()
十七、 过程:
a) 创建线程,设参数为CREATE_SUSPENDED.
b) 设置CWinThread()中的m_bAutoDelet.防止创建失败后自动DELETE线程句柄。可能会产生race condition错误。
c) 调用ResumeThread()激活线程。
十八、 不要再UI线程间共享UI对象。会很卡。
十九、 MFC中的每一步都要做到错误检测