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

来源:互联网 发布:汽车模拟改装软件 编辑:程序博客网 时间:2024/05/17 23:21

设计目标:

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

原始程序:

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

using namespace std;

int total=100;

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

DWORD WINAPI proc2(
LPVOID lpParameter   // thread data
){
    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 data
){
    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 data
){
    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 data
){
    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 data
){
    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 data
){
    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 data
){
    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 data
){
    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 data
){
    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的信息,或者反之。

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

总结:

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

最后一种临界区,消耗资源非常少,速度快。
但是只能解决线程间的同步。

本文链接:http://hplonline20100103.blog.163.com/blog/static/1361364342010040040921/

原创粉丝点击