为什么使用线程同步?
同步可以保证在一个时间内只有一个线程对某个资源(如操作系统资源等共享资源)有控制权。共享资源包括全局变量、公共数据成员或者句柄等。同步还可以使得有关联交互作用的代码按一定的顺序执行。
线程同步的方式?
同步对象有:CRITICAL_SECTION (临界区),Event(事件),Mutex(互斥对象),Semaphores(信号量)。
本文重点讲解CRITICAL_SECTION (临界区)。
临界区,说白了,就是“锁”。看过星爷的《破坏王》的朋友都知道,那个送外卖的小子,就是靠自创绝招“无敌风火轮”将大师兄打败,抱得美人归。“无敌风火轮”的本质就是:锁!
怎么锁?
这里有四个关键函数:InitializeCriticalSection EnterCriticalSection LeaveCriticalSection DeleteCriticalSection来完成此机制。
使用临界区对象的时候,首先要定义一个临界区对象CriticalSection:
CRITICAL_SECTION CriticalSection;
然后,初始化该对象:InitializeCriticalSection(&CriticalSection);
如果一段程序代码需要对某个资源进行同步保护,则这是一段临界区代码。在进入该临界区代码前调用EnterCriticalSection函数,这样,其他线程都不能执行该段代码,若它们试图执行就会被阻塞。
完成临界区的执行之后,调用LeaveCriticalSection函数,其他的线程就可以继续执行该段代码。
简要实例
下面的代码中,如果不加CRITICAL_SECTION ,有可能造成在线程1给data设置完名字后,线程2给data设置年龄,造成了数据紊乱,所以有必要使用同步机制,将其锁住,保证数据的安全。
class Data { private: CString Name; int Age; public: void SetName(const CString& name) { Name = name; } void SetAge(int age) { Age = age; } void GetName(CString &name) { name = Name; } void GetAge(int &age) { age = Age; } }; Data g_data; //全局变量 CRITICAL_SECTION CriticalSection; //线程函数 DWORD WINAPI ThreadProc( LPVOID lpParameter ) { EnterCriticalSection(&CriticalSection); data.SetName("赵星星"); data.SetAge(20); LeaveCriticalSection(&CriticalSection); } int main() { InitializeCriticalSection(&CriticalSection); //创建线程,执行线程函数 //...... DeleteCriticalSection(&CriticalSection); return 0; }真锁?假锁?
可以定义CRITICAL_SECTION 数组:CRITICAL_SECTION g_Critical[10];
CRITICAL_SECTION 没有超时的概念,如果函数LeaveCriticalSection不被调用,则其他线程将无限期的等待。容易造成死锁。
CRITICAL_SECTION 属于轻量级的线程同步对象,相对于mutex来说,它的效率会高很多。mutex可以用于进程之间的同步,CRITICAL_SECTION只在同一个进程有效。
实际上,CRITICAL_SECTION 锁的是代码段,如果代码段中有对资源的占用,只是间接的锁住了该资源,我们也可以称之为“假锁”。
参考:
1、《MFC教程》
2、 csdn精彩讨论