现在的位置: 首页 > 综合 > 正文

win32多线程程序设计笔记(第四章上)

2018年10月01日 ⁄ 综合 ⁄ 共 3183字 ⁄ 字号 评论关闭

           前面已经介绍了线程的创建、销毁过程,如何判断一个线程是否结束;但是撰写多线程程序的一个挑战性问题就是:如何让一个线程和另外一个线程合作。

        在同一时间段会存在多个线程,当这些线程同时存取同一数据时,就会有问题。就像在超市储物品一样,来的时候物品箱是空,转身拿物品准备储的时候,发现物品箱已被占用了。这时,物品箱就是我所说的同一数据,人指的就是线程了。

        线程之间的协调工作由同步机制来完成。同步机制相当于线程之间的红绿灯系统,负责给某个线程绿灯而给其他线程红灯进行等待。

:对同步(synchronous)和异步进行一个说明,所谓的同步:当程序1调用程序2时,程序1停下不动,直到程序2完成回到程序1来,程序1才继续下去。

       Win32 API中SendMessage()就是同步行为,而PostMessage()就是异步行为。

现在,看看第一个同步机制。


一、Critical Sections(临界区域、关键区域)


主要操作有:

        InitializeCriticalSection

        EnterCriticalSection

        LeaveCriticalSection

        DeleteCriticalSection

通过一个例子说明:

CRITICAL_SECTION gBoxKey ;

DWORD WINAPI ThreadFun(LPVOID n){
       //进入关键区域(情景:关上物品箱,拨下钥匙)
       EnterCreiticalSection (&gBoxKey ); //()

       //处理一些不可分割的操作。。。。。
       //(情景:转身拿物品,储物品,去购物。。。。)

       //离开关键区域(情景:打开物品箱,拿出储存的物品,插上钥匙)               
       LeaveCreiticalSection (&gBoxKey); //()
}
 
void main(){
       //初始化全局锁(情景:生成物品箱的钥匙 )
       InitializeCriticalSection( &gBoxKey ) ;            
       
       //产生两个线程(情景:准备两个人抢一个物品箱 )
       HANDLE hMan1 = CreateThread(NULL,0,ThreadFun, ……);
       HANDLE hMan2 = CreateThread(NULL,0,ThreadFun, ……);
 
       CloseHandle(hMan1);
       CloseHandle(hMan2);
 
       //删除全局锁(情景:删除物品箱的钥匙 )
       DeleteCriticalSection( &gBoxKey ) ;
}

注意:1、一旦一个线程进入一个critical section,它能够重复进入该critical section,但每次进入都有对应退出;
             2、很难定义最小锁定时间,如果资源一直被锁定,你就会阻止其他线程的执行,所以千万不要在critical section中调用Sleep()或任何Wait函数。

死锁问题

在使用临界区域的时候,有可能出现两个线程互相等待对方的资源从而形成等待的轮回,这种情况称为“死锁”。下面总结一下产生死锁的原因等。

产生死锁的原因主要是
    (1) 因为系统资源不足。
    (2) 进程运行推进的顺序不合适。
    (3) 资源分配不当等。
产生死锁的四个必要条件
    (1)互斥条件:一个资源每次只能被一个进程使用。
    (2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
    (3)不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
    (4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
避免死锁
        死锁的预防是通过破坏产生条件来阻止死锁的产生,但这种方法破坏了系统的并行性和并发性。
        死锁产生的前三个条件是死锁产生的必要条件,也就是说要产生死锁必须具备的条件,而不是存在这3个条件就一定产生死锁,那么只要在逻辑上回避了第四个条件就可以避免死锁。
        避免死锁采用的是允许前三个条件存在,但通过合理的资源分配算法来确保永远不会形成环形等待的封闭进程链,从而避免死锁。该方法支持多个进程的并行执行,为了避免死锁,系统动态的确定是否分配一个资源给请求的进程。
预防死锁:具体的做法是破坏产生死锁的四个必要条件之一。

二、Mutexes(互斥器)

        一个时间内只能有一个线程拥有mutex,就像同一时间内只能有一个线程进入同一个Critical Section一样。无论从共享资源的思路了,还是从程序代码的编制上,使用Mutexes与使用Critical Sections几乎都没有什么区别;
当然,mutex和critical section还有一些区别
       1、锁住一个未被拥有的mutex,比锁住一个未被拥有的critical section,需要花费几乎100倍的时间;
       2、mutex可以跨进程使用,critical section只能在同一进程中使用
       3、等待一个mutex时,可以指定“结束等待”的时间长度,这样就避免了进入在锁住过程中出不来的问题(这一点下面接着说明)。


提出问题:       
        作为 Mutexes机制的提出肯定是有其原因的;我们来看这样的一个情形,当我拿走钥匙以后,因为某些因素再也不能回来了,那么这个箱子便再也不能被使用。也就是说,进入Critical
Sections
线程若中途当掉了,那么别了线程是再也进不了Critical Sections(一个资源就这样浪费了),那些需要进入Critical Sections的线程会停在入口不再执行,线程永远都结束不了。

解决

        还记得上一章学过的WaitForSingleObject吗?上一章主要用它等待线程的结束,但这个函数的作用不仅限于此,在这里,我们再前进一小步,探究WaitForSingleObject这个函数的妙用。

在这里,我遇到了一个叫mutex核心对象,mutex对激发的定义是:“当没有任何线程拥有该mutex,而且有一个线程正以Wait…()等待该mutex,该mutex就会短暂地出现激发状态,使Wait…()得以返回, 那么在其它的情况,mutex处于未激发状态”。
        好了,我们又进一步的了解了WaitForSingleObject函数,那么,如何解决Critical Sections所遇到的因难呢?当拥有mutex的线程结束前没有调用ReleaseMutex(不管该线程是当了,还是忘记调用ReleaseMutex),那么其它正以WaitForSingleObject()等待此mutex的线程就会收到WAIT_ABANDONED_0。有了这个值,我就解开难题了。

举例说明

HANDLE hBoxKey;

DWORD WINAPI ThreadFun(LPVOID n){
       //进入关键区域(情景:关上物品箱,拨下钥匙)
       WaitForSingleObject ( hMutex,INFINITE ); //
           //处理一些不可分割的操作。。。。。
           //(情景:转身拿物品,储物品,去购物。。。。)
       //离开关键区域(情景:打开物品箱,拿出储存的物品,插上钥匙)               
       ReleaseMutex ( hMutex ); //
}
 
void main(){
       //初始化全局锁(情景:生成物品箱的钥匙 )
       hBoxKey = CreateMutex( NULL,FALSE,NULL );         
       
       //产生两个线程(情景:准备两个人抢一个物品箱 )
       HANDLE hMan1 = CreateThread(NULL,0,ThreadFun, ……);
       HANDLE hMan2 = CreateThread(NULL,0,ThreadFun, ……);
 
       CloseHandle(hMan1);
       CloseHandle(hMan2);
 
       //删除全局锁(情景:删除物品箱的钥匙 )
       CloseHandle( hBoxKey ) ;
}

抱歉!评论已关闭.