线程知识汇总
参考资源:http://blog.csdn.net/morewindows/article/details/7538247
线程的好处:
使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。
使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。
线程的互斥
当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
线程(进程)之间的制约关系
当线程并发执行时,由于资源共享和线程协作,使用线程之间会存在以下两种制约关系。
(1).间接相互制约。一个系统中的多个线程必然要共享某种系统资源,如共享CPU,共享I/O设备,所谓间接相互制约即源于这种资源共享,打印机就是最好的例子,线程A在使用打印机时,其它线程都要等待。
(2).直接相互制约。这种制约主要是因为线程之间的合作,如有线程A将计算结果提供给线程B作进一步处理,那么线程B在线程A将数据送达之前都将处于阻塞状态。
间接相互制约可以称为互斥,直接相互制约可以称为同步,对于互斥可以这样理解,线程A和线程B互斥访问某个资源则它们之间就会产个顺序问题——要么线程A等待线程B操作完毕,要么线程B等待线程操作完毕,这其实就是线程的同步了。因此同步包括互斥,互斥其实是一种特殊的同步。
线程函数:
Win32函数库中提供了操作多线程的函数,包括创建线程、终止线程、建立互斥区等。
创建新的线程的函数如下:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId);
第一个参数表示线程内核对象的安全属性,一般传入NULL表示使用默认设置。
第二个参数表示线程栈空间大小。传入0表示使用默认大小(1MB)。
第三个参数表示新线程所执行的线程函数地址,多个线程可以使用同一个函数地址。
第四个参数是传给线程函数的参数。
第五个参数指定额外的标志来控制线程的创建,为0表示线程创建之后立即就可以进行调度,如果为CREATE_SUSPENDED则表示线程创建后暂停运行,这样它就无法调度,直到调用ResumeThread()。
第六个参数将返回线程的ID号,传入NULL表示不需要返回该线程ID号。
如果创建成功则返回线程的句柄,否则返回NULL。
创建了新的线程后,该线程就开始启动执行了。
但如果在dwCreationFlags中使用了CREATE_SUSPENDED特性,那么线程并不马上执行,而是先挂起,等到调用ResumeThread后才开始启动线程。
在这个过程中可以调用下面这个函数来设置线程的优先权:
BOOL SetThreadPriority(HANDLE hThread,int nPriority);
当调用线程的函数返回后,线程自动终止。如果需要在线程的执行过程中终止则可调用函数:
VOID ExitThread(DWORD dwExitCode);
如果在线程的外面终止线程,则可调用下面的函数:
BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);
但应注意: 该函数可能会引起系统不稳定,而且线程所占用的资源也不释放。因此,一般情况下,建议不要使用该函数。
线程的同步:
Win32 API提供了一组能使线程阻塞其自身执行的等待函数。这些函数在其参数中的一个或多个同步对象产生了信号,或者超过规定的等待时间才会返回。在等待函数未返回时,线程处于等待状态,此时线程只消耗很少的CPU时间。使用等待函数既可以保证线程的同步,又可以提高程序的运行效率。最常用的等待函数是:
DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);
hHandle: 指向进程句柄
dwMilliseconds: 等待时间(以毫秒计算)。如果设置值为INFINITE,表示永远等待下去(无限时间量INFINITE已经定义为0xFFFFFFFF或-1),设置值为0,立刻返回。一般设置为一个>0的时间。
而函数WaitForMultipleObject可以用来同时监测多个同步对象,该函数的声明为:
DWORD WaitForMultipleObject(DWORD nCount,CONST HANDLE *lpHandles,BOOL bWaitAll,DWORD dwMilliseconds);
nCount:查看的内核对象的数量。这个值必须在1 – 64 之间。
phObjects:参数是指向进程句柄的数组的指针。HANDLE h[3];
fWaitAll: 直到所有指定的内核对象都变为已通知状态才返回。否则,只要有一个为已通知,就返回。
返回值:
WAIT_OBJECT_0: 表示线程等待的对象变为已通知状态。
如果为fWaitAll传递FALSE,那么一旦任何一个对象变为已通知状态,该函数便返回。在这种情况下,你可能想要知道哪个对象变为已通知状态。返回值是WAIT_OBJECT_0 与(WAIT_OBJECT_0 + dwCount-1)之间的一个值。换句话说,如果返回值不是WAIT_TIMEOUT,也不是WAIT_FAILED,那么应该从返回值中减去WAIT_OBJECT_0。产生的数字是作为第二个参数传递给WaitForMultipleObjects的句柄数组中的索引。该索引说明哪个对象变为已通知状态。
WAIT_TIMEOUT: 表示设置的超时已经到期。
WAIT_FAILED: 表示将一个错误的值(如一个无效句柄,进程不存在)传递给WaitForSingleObject。
互斥关键段CRITICAL_SECTION
关键段变量可以用于线程间的互斥,但不可以用于同步。
初始化:void InitializeCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
销毁:void DeleteCriticalSection(LPCRITICAL_SECTIONlpCriticalSection); 用完之后记得销毁。
进入关键区域:void EnterCriticalSection(LPCRITICAL_SECTIONlpCriticalSection); 保证各线程互斥
离开关关键区域: void LeaveCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
互斥体对象 Mutex
Mutex对象互斥锁,来保证共享数据操作的完整性。个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
Mutex对象的状态在它不被任何线程拥有时才有信号,而当它被拥有时则无信号。Mutex对象很适合用来协调多个线程对共享资源的互斥访问。
可按下列步骤使用该对象:
HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes,BOOL bInitialOwner, LPCTSTRlpName);
第一个参数表示安全控制,一般直接传入NULL。
第二个参数用来确定互斥量的初始拥有者。如果传入TRUE表示互斥量对象内部会记录创建它的线程的线程ID号并将递归计数设置为1,由于该线程ID非零,所以互斥量处于未触发状态。如果传入FALSE,那么互斥量对象内部的线程ID号将设置为NULL,递归计数设置为0,这意味互斥量不为任何线程占用,处于触发状态。
第三个参数用来设置互斥量的名称,在多个进程中的线程就是通过名称来确保它们访问的是同一个互斥量。
函数访问值:
成功返回一个表示互斥量的句柄,失败返回NULL。
HANDLE OpenMutex( DWORDdwDesiredAccess, BOOLbInheritHandle, LPCTSTRlpName //名称);
第一个参数表示访问权限,对互斥量一般传入MUTEX_ALL_ACCESS请求对互斥体的完全访问。MUTEX_MODIFY_STATE 允许使用 ReleaseMutex 函数SYNCHRONIZE 允许互斥体对象同步使用
第二个参数表示互斥量句柄继承性,如希望子进程能够继承句柄 一般传入TRUE即可。
第三个参数表示名称。某一个进程中的线程创建互斥量后,其它进程中的线程就可以通过这个函数来找到这个互斥量。
函数访问值:成功返回一个表示互斥量的句柄,失败返回NULL
然后,在线程可能产生冲突的区域前(即访问共享资源之前)调用WaitForSingleObject,将句柄传给函数,请求占用互斥对象:
dwWaitResult = WaitForSingleObject(hMutex,5000L);
共享资源访问结束,释放对互斥体对象的占用:
ReleaseMutex(hMutex); Mutex 享有现成权力,ReleaseMutex 只能释放本线程的 Mutex
最后一个清理互斥量,由于互斥量是内核对象,因此使用
CloseHandle()
与关键段类似,互斥量也是不能解决线程间的同步问题。关键段与互斥量都有线程所有权的限定。
1.互斥量是内核对象,它与关键段都有“线程所有权”所以不能用于线程的同步。
2.互斥量能够用于多个进程之间线程互斥问题,并且能完美的解决某进程意外终止所造成的“遗弃”问题。
另外由于互斥量常用于多进程之间的线程互斥,所以它比关键段还多一个很有用的特性——“遗弃”情况的处理。比如有一个占用互斥量的线程在调用ReleaseMutex()触发互斥量前就意外终止了(相当于该互斥量被“遗弃”了),那么所有等待这个互斥量的线程是否会由于该互斥量无法被触发而陷入一个无穷的等待过程中了?这显然不合理。因为占用某个互斥量的线程既然终止了那足以证明它不再使用被该互斥量保护的资源,所以这些资源完全并且应当被其它线程来使用。因此在这种“遗弃”情况下,系统自动把该互斥量内部的线程ID设置为0,并将它的递归计数器复置为0,表示这个互斥量被触发了。然后系统将“公平地”选定一个等待线程来完成调度(被选中的线程的WaitForSingleObject()会返回WAIT_ABANDONED_0)。
Mutex物件的做法与特性如下:
使用CreateMutex()来产生一个Mutex物件,而传入的Mutex名称字串用以区别不同的Mutex,也就是说,不管是哪个Process/Thread,只要传入的名称叁数是相同的一个字串,那CreateMutex()传回值(hMutex, handle of Mutex)会指向相同的一个Mutex物件。这和Event物件相同。然而Mutex和Event有很大的不同,Mutex有Owner的概念,如果Mutex为ThreadA所拥有,那麽ThreadA执行WaitForSingleObject()时,并不会停下来,而会立即传回WAIT_OBJECT_0,而其他的Thread执行WaitForSingleObject()则会停下来,直到Mutex的所有权被Release出来或Time Out。而Thread如何取得Mutex的所有权呢?主要如下:
1.CreateMutex(Byval 0, 1, "MyMutex") 第二个叁数传1进去,则第一个呼叫CreateMutex 且第二个叁数传1的Thread会拥有该Mutex。但如果第二个叁数传0进去,那代表 CreateMutex时,没有人拥有该Mutex。
2.承上的说明,如果Mutex没有拥有者,则第一个呼叫WaitForSingleObject的Thread 会拥有该Mutex。
上面说过,只有拥有该Mutex的Thread在执行WaitForSingleObject()不会停下来,其他的Thread则会停下来,那其他的Thread如何取得该Mutex的所有权呢?那必需是原先拥有该Mutex的Thread以ReleaseMutex来放弃所有权,一旦所有权放出来,而有其他的Thread处於WaitForSingleObject()的停留等待状态,则有一个Thread会即时取得该Mutex的所有权(上面第2点的说明),所以,若其他的Thread也执行WaitForSingleObject()时,就会处於等待的状态。正因WaitForSingleObject()会令Mutex处於UnSignal的状态(和Event不同),所以可以完成同一时问只有一个thread来UpDate共用记忆体的需求(当然大家都要使用Mutex
的规则则来做,即想Update时,要使用WaitForSingleObject()来看看是否可取得Mutex的所
有权。)
另有一件事要特别提出,如果一个Thread已取得Mutex的所有权,而它呼叫WaitForSingleObject()n 次,则也要使用ReleaseMutex n次才能够将Mutex的拥有权放弃,这和Event也不同,而且,非Mutex拥有者呼叫ReleaseMutex也不会有任何作用。而每次以WaitForSingleObject呼叫一次,Mutex会有一个计数器会加一,ReleaseMutex成功会减一,直到Mutex的计数器为0之後,系统才会将之去除。
同步互斥事件 Event
创建事件
HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName);
第一个参数表示安全控制,一般直接传入NULL。
第二个参数确定事件是手动置位还是自动置位,传入TRUE表示手动置位,传入FALSE表示自动置位。如果为自动置位,则对该事件调用WaitForSingleObject()后会自动调用ResetEvent()使事件变成未触发状态。打个小小比方,手动置位事件相当于教室门,教室门一旦打开(被触发),所以有人都可以进入直到老师去关上教室门(事件变成未触发)。自动置位事件就相当于医院里拍X光的房间门,门打开后只能进入一个人,这个人进去后会将门关上,其它人不能进入除非门重新被打开(事件重新被触发)。
第三个参数表示事件的初始状态,传入TRUR表示已触发。
第四个参数表示事件的名称,传入NULL表示匿名事件。
根据名称获得一个事件句柄。
HANDLE OpenEvent(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName //名称);
第一个参数表示访问权限,对事件一般传入EVENT_ALL_ACCESS。详细解释可以查看MSDN文档。
第二个参数表示事件句柄继承性,一般传入TRUE即可。
第三个参数表示名称,不同进程中的各线程可以通过名称来确保它们访问同一个事件。
触发事件
BOOL SetEvent(HANDLE hEvent);
每次触发后,必有一个或多个处于等待状态下的线程变成可调度状态。
将事件设为末触发
BOOL ResetEvent(HANDLE hEvent);
最后一个事件的清理与销毁
CloseHandle()
触发一个事件脉冲:将事件触发后立即将事件设置为未触发,相当于触发一个事件脉冲。
BOOL PulseEvent(HANDLE hEvent);
函数说明:这是一个不常用的事件函数,此函数相当于SetEvent()后立即调用ResetEvent();此时情况可以分为两种:
1.对于手动置位事件,所有正处于等待状态下线程都变成可调度状态。
2.对于自动置位事件,所有正处于等待状态下线程只有一个变成可调度状态。
此后事件是末触发的。该函数不稳定,因为无法预知在调用PulseEvent ()时哪些线程正处于等待状态。
1.事件是内核对象,事件分为手动置位事件和自动置位事件。事件Event内部它包含一个使用计数(所有内核对象都有),一个布尔值表示是手动置位事件还是自动置位事件,另一个布尔值用来表示事件有无触发。
2.事件可以由SetEvent()来触发,由ResetEvent()来设成未触发。还可以由PulseEvent()来发出一个事件脉冲。
3.事件可以解决线程间同步问题,因此也能解决互斥问题。
同步互斥信息号Semaphore
创建信号量
HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName);
第一个参数表示安全控制,一般直接传入NULL。
第二个参数表示初始资源数量。
第三个参数表示最大并发数量。
第四个参数表示信号量的名称,传入NULL表示匿名信号量。
打开信号量
HANDLE OpenSemaphore(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName);
第一个参数表示访问权限,对一般传入SEMAPHORE_ALL_ACCESS。详细解释可以查看MSDN文档。
第二个参数表示信号量句柄继承性,一般传入TRUE即可。
第三个参数表示名称,不同进程中的各线程可以通过名称来确保它们访问同一个信号量。
递增信号量的当前资源计数
BOOL ReleaseSemaphore( HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount);
第一个参数是信号量的句柄。
第二个参数表示增加个数,必须大于0且不超过最大资源数量。
第三个参数可以用来传出先前的资源计数,设为NULL表示不需要传出。
注意:当前资源数量大于0,表示信号量处于触发,等于0表示资源已经耗尽故信号量处于末触发。在对信号量调用等待函数时,等待函数会检查信号量的当前资源计数,如果大于0(即信号量处于触发状态),减1后返回让调用线程继续执行。一个线程可以多次调用等待函数来减小信号量。
信号量的清理与销毁 CloseHandle()
信号量也可以解决线程之间的同步问题。由于信号量可以计算资源当前剩余量并根据当前剩余量与零比较来决定信号量是处于触发状态或是未触发状态,因此信号量的应用范围相当广泛。
原文:http://my.oschina.net/isixth/blog/390970