几种同步手段(互斥量,信号量,事件,临界区)

(hplonline)2009.7.10

环境:

VISTA+VC6
双核

这个环境对于下面的有些效果来说,十分关键。
在我下面的练习中,如果是单核,那么两个线程无法真正的同时执行,
而单个操作的耗时也并不长,可能看不到互斥访问中的一些问题。
在VISTA之前的一些系统,时间片比较大,也不容易看到。。

设计目标:


模拟一个售票系统,有两个线程可以出售,总共100张票。
中间打印出出售的信息。
这里的票是一个临界资源,
同时,控制台也是个临界资源。(如果同时输出会造成屏幕的混乱)

原始程序:

#include <stdio.h>
#include <iostream>
#include <windows.h>

using namespace std;

int total=100;

DWORD WINAPI proc1(
LPVOID lpParameter   // thread da ta
){
    while (1){
        if ( total == 0 ) break ;
         Sleep(rand()%30) ;
        cout<<"thread1 sold:"<<total--<<endl;
    }
    return 0;
};

DWORD WINAPI proc2(
LPVOID lpParameter   // thread da ta
){
    while (1){
        if ( total == 0 ) break ;
        Sleep(rand()%30) ;
        cout<<"thread2 sold:"<<total--<<endl;
    }
    return 0;
};

int main(){
    HANDLE thread1,thread2;
    thread1=CreateThread(NULL,0,proc1,NULL,0,NULL);
    thread2=CreateThread(NULL,0,proc2,NULL,0,NULL);
    Sleep(4000);
    CloseHandle(thread1);
    CloseHandle(thread2);
    return 0;
}

程序的意思很直观,就是开了两个线程。
在里面分别判断票数是否到0,
如果不是的话,那么模拟售出了一张票,并且打印出售出的票号。

中间标红的随机延迟是一个关键点。
把他去掉的话,一般就看不到效果了。
因为电脑实在太快了,if的判断和下面的输出,
几乎是在同一时间完成的。
从时间片的意义上来说,大部分时候可以看做原子操作。
于是减到0之后,线程正常结束就停下了。
所以给个随机延迟,强迫if的判断和total--的分离,
这样就可以看到由于没有做好同步造成的问题了。

这个程序的输出,有的地方会有字符交叉,很混乱。
最明显的是,减到0之后,还会不断地向下面减。

同步的框架:

下面几个方法,大同小异,
基本上的过程就是:

1.定义相关的变量
2.创建相关的变量
3.进去临界区前等待相关的信号
4.退出的时候 清除 相关的信号
(信号有的时候可以进入临界区,还是信号无的时候可以进入,
在几个实现手段里面有不同的叙述,所以清除是个泛化的说法)

互斥量:

#include <stdio.h>
#include <iostream>
#include <windows.h>

using namespace std;

int total=100;
HANDLE mutex;

DWORD WINAPI proc1(
LPVOID lpParameter   // thread da ta
){
    while (1){
       WaitForSingleObject(mutex,INFINITE);
        if ( total == 0 ) break ;
        Sleep(rand()%30) ;
        cout<<"thread1 sold:"<<total--<<endl;
        ReleaseMutex(mutex);
    }
    return 0;
};

DWORD WINAPI proc2(
LPVOID lpParameter   // thread da ta
){
    while (1){
        WaitForSingleObject(mutex,INFINITE);
        if ( total == 0 ) break ;
        Sleep(rand()%30) ;
        cout<<"thread2 sold:"<<total--<<endl;
        ReleaseMutex(mutex);
    }
    return 0;
};

int main(){
    HANDLE thread1,thread2;
    mutex=CreateMutex(NULL,false,NULL);
    thread1=CreateThread(NULL,0,proc1,NULL,0,NULL);
    thread2=CreateThread(NULL,0,proc2,NULL,0,NULL);
    Sleep(4000);
    CloseHandle(thread1);
    CloseHandle(thread2);
    CloseHandle(mutex);
    return 0;
}

这是最基本的,和框架非常吻合,
知道标红的几个函数就按照这种方式写就行了。

信号量:

#include <stdio.h>
#include <iostream>
#include <windows.h>

using namespace std;

int total=100;

HANDLE semaphore ;


DWORD WINAPI proc1(
LPVOID lpParameter   // thread da ta
){
    while (1){
         WaitForSingleObject(semaphore,INFINITE);
        if ( total == 0 ) break ;
        Sleep(rand()%30) ;
        cout<<"thread1 sold:"<<total--<<endl;
         ReleaseSemaphore(semaphore , 1 , NULL) ;
    }
    return 0;
};

DWORD WINAPI proc2(
LPVOID lpParameter   // thread da ta
){
    while (total>0){
        WaitForSingleObject(semaphore,INFINITE);
        if ( total == 0 ) break ;
        Sleep(rand()%30) ;
        cout<<"thread2 sold:"<<total--<<endl;
        ReleaseSemaphore(semaphore , 1 , NULL) ;
    }
    return 0;
};

int main(){
    HANDLE thread1,thread2;
     semaphore = CreateSemaphore(NULL , 1 , 1 , NULL) ;

    thread1=CreateThread(NULL,0,proc1,NULL,0,NULL);
    thread2=CreateThread(NULL,0,proc2,NULL,0,NULL);

    Sleep(4000);
    CloseHandle(thread1);
    CloseHandle(thread2);
     CloseHandle(semaphore) ;

    return 0;
}

和互斥量不同的地方在于,信号量可以允许多个线程同时访问。
比如writer/reader模型中,多个reader同时访问是允许的。
在创建的时候,可以指定最大的数目和初始化时候的数目。
如果指定为1,也就是这里用的情况,相当于就和前面的互斥量方式一样了。

事件:

#include <stdio.h>
#include <iostream>
#include <windows.h>

using namespace std;

int total=100;

HANDLE event;

DWORD WINAPI proc1(
LPVOID lpParameter   // thread da ta
){
    while (1){
         WaitForSingleObject(event,INFINITE);
        if ( total == 0 ) break ;
        Sleep(rand()%30) ;
        cout<<"thread1 sold:"<<total--<<endl;
         SetEvent(event) ;
    }
    return 0;
};

DWORD WINAPI proc2(
LPVOID lpParameter   // thread da ta
){
    while (total>0){
        WaitForSingleObject(event,INFINITE);
        if ( total == 0 ) break ;
        Sleep(rand()%30) ;
        cout<<"thread2 sold:"<<total--<<endl;
        SetEvent(event) ;
    }
    return 0;
};

int main(){
    HANDLE thread1,thread2;
     event = CreateEvent(NULL , FALSE , TRUE , NULL) ;
    thread1=CreateThread(NULL,0,proc1,NULL,0,NULL);
    thread2=CreateThread(NULL,0,proc2,NULL,0,NULL);
    Sleep(4000);
    CloseHandle(thread1);
    CloseHandle(thread2);
     CloseHandle(event);
    return 0;
}

CreateEvent的第二个参数是设置是否为手动事件。
如果是手动的话,当用WaitForSingleObject等到事件的时候,
系统并不清除掉该事件已发生的信号,
于是要自己调用ResetEvent来清除。
这两个函数之间的空隙将造成潜在的同步问题。
于是设置生FALSE,表示自动事件。
在等到该事件的时候,同时把该事件置为无效,防止其他地方进入临界段。

临界区:

#include <stdio.h>
#include <iostream>
#include <windows.h>

using namespace std;

int total=100;

CRITICAL_SECTION _cs;

DWORD WINAPI proc1(
LPVOID lpParameter   // thread da ta
){
    while (1){
        EnterCriticalSection(&_cs);
        if ( total == 0 ) break ;
    //    Sleep(rand()%30) ;
        cout<<"thread1 sold:"<<total--<<endl;
         LeaveCriticalSection(&_cs);
    }
    return 0;
};

DWORD WINAPI proc2(
LPVOID lpParameter   // thread da ta
){
    while (1){
        EnterCriticalSection(&_cs);
        if ( total == 0 ) break ;
    //    Sleep(rand()%30) ;
        cout<<"thread2 sold:"<<total--<<endl;
        LeaveCriticalSection(&_cs);
    }
    return 0;
};

int main(){
    HANDLE thread1,thread2;
    InitializeCriticalSection(&_cs);
    thread1=CreateThread(NULL,0,proc1,NULL,0,NULL);
    thread2=CreateThread(NULL,0,proc2,NULL,0,NULL);
    Sleep(4000);
    CloseHandle(thread1);
    CloseHandle(thread2);
    return 0;
}

与前面的相比,这种方式在最后不用类似CloseHandle之类的操作。

还有注意到我把上面的Sleep注释掉了。
因为 使用临界区来同步,速度非常快,消耗资源比前几种都小
加上随机延迟后,可能一个线程直接就把票给售完了。。

即使在现在这种写法下,可能运行好几次,
能够找到一下若干thread1信息之内夹杂几个thread2的信息,或者反之。

但观察前三种,基本上的效果是一个线程输出一下,交织频繁。

总结:

前三种方式,依赖一个句柄,
他们都可以指定一个名字,成为全局的对象,
可以完成进程间的同步。
在不用的时候要,销毁相关的句柄。
消耗资源比较大。

最后一种临界区,消耗资源非常少,速度快。
但是只能解决线程间的同步
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值