所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,同时其它线程也不能调用这个方法。按照这个定义,其实绝大多数函数都是同步调用(例如sin, isdigit等)。但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。最常见的例子就是 SendMessage。该函数发送一个消息给某个窗口,在对方处理完消息之前,这个函数不返回。当对方处理完毕以后,该函数才把消息处理函数所返回的 LRESULT值返回给调用者。
在多线程编程里面,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。在Java里面,通过synchronized 进行同步的保证。
协作的例子,进程、线程同步,可理解为进程或线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B依言执行,再将结果给A;A再继续操作。
例如,对于银行系统的计算机来说,可能使用一个线程来更新其用户数据库,而用另外一个线程来读取数据库以响应储户的需要,极有可能读数据库的线程读取的是未完全更新的数据库,因为可能在读的时候只有一部分数据被更新过。使隶属于同一进程的各线程协调一致地工作称为线程的同步。MFC提供了多种同步对象,下面我们只介绍最常用的四种:
1〉临界区(CCriticalSection)
创建公共的临界区CCriticalSection对象,通过其成员函数Lock()/Unlock()来进行锁定与解锁的控制。同一个临界区,在不同线程A、B访问时候,只有等正在访问的线程A把临界区Unlock之后,B才能继续访问该临界区。
2〉事件(CEvent)
CEvent类提供了对事件的支持。事件是一个允许一个线程在某种情况发生时,唤醒另外一个线程的同步对象。每一个CEvent 对象可以有两种状态:有信号状态和无信号状态。线程监视位于其中的CEvent类对象的状态,并在相应的时候采取相应的操作。
在MFC中,CEvent 类对象有两种类型:人工(手动)事件和自动事件。一个自动CEvent对象在被至少一个线程释放后会自动返回到无信号状态;而人工事件对象获得信号后,释放可利用线程,但直到调用成员函数ReSetEvent()才将其设置为无信号状态。在创建CEvent类的对象时,默认创建的是自动事件。Event有三个成员函数:
a)、CEvent(BOOL bInitiallyOwn=FALSE,BOOL bManualReset=FALSE, LPCTSTR lpszName=NULL,
LPSECURITY_ATTRIBUTES lpsaAttribute=NULL);
bInitiallyOwn:指定事件对象初始化状态,TRUE为有信号,FALSE为无信号;
bManualReset:指定要创建的事件是属于人工事件还是自动事件。TRUE为人工事件,FALSE为自动事件;
后两个参数一般设为NULL,在此不作过多说明。
b)、BOOL CEvent::SetEvent();
将 CEvent 类对象的状态设置为有信号状态。如果事件是人工事件,则 CEvent 类对象保持为有信号状态,直到调用成员函数ResetEvent()将其重新设为无信号状态时为止。如果CEvent 类对象为自动事件,则在SetEvent()将事件设置为有信号状态后,CEvent类对象由系统自动重置为无信号状态。如果该函数执行成功,则返回非零值,否则返回零。
c)、BOOL CEvent::ResetEvent();
该函数将事件的状态设置为无信号状态,并保持该状态直至SetEvent()被调用时为止。由于自动事件是由系统自动重置,故自动事件不需要调用该函数。如果该函数执行成功,返回非零值,否则返回零。
我们一般通过调用WaitForSingleObject函数来监视事件状态。
3〉互斥量(CMutex)
互斥对象与临界区对象很像.互斥对象与临界区对象的不同在于:互斥对象可以在进程间使用,而临界区对象只能在同一进程的各线程间使用
。当然,互斥对象也可以用于同一进程的各个线程间,但是在这种情况下,使用临界区会更节省系统资源,更有效率。
4〉信号量(CSemaphore)
当需要一个计数器来限制可以使用某个资源的线程的数目时,可以使用信号量对象。CSemaphore类的对象保存了对当前访问某一指定资源的线程的计数值,该计数值是当前还可以使用该资源的线程的数目。如果这个技术达到了零,则所有对这个CSemaphore类对象所控制的资源的访问尝试都被放入到一个队列中等待,直到超时或者计数值不为零时为止。一个线程释放已被访问了的被保护的资源时,计数值减1,一个线程完成了对被控共享资源的访问时,技术值增1.这个被CSemaphore类对象所控制的资源可以同时接受访问的最大线程数在该对象的构建函数中指定。
CSemaphore 类的构造函数原型及参数说明如下:
CSemaphore (LONG lInitialCount=1,
LONG lMaxCount=1,
LPCTSTR pstrName=NULL,
LPSECURITY_ATTRIBUTES lpsaAttributes=NULL);
lInitialCount:信号量对象的初始计数值,即可访问线程数目的初始值;
lMaxCount:信号量对象计数值的最大值,该参数决定了同一时刻可访问由信号量保护的资源的线程最大数目;
后两个参数在同一进程中使用一般为NULL,不作过多讨论;
在用CSemaphore类的构造函数创建信号量对象时要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时,则说明当前占用资源的线程数已经达到了所允许的最大数目,不能再允许其它线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源数加1。