Win32线程——同步机制

来源:互联网 发布:开淘宝企业店流程 编辑:程序博客网 时间:2024/05/17 04:20

《Win32多线程程序设计》–Jim Beveridge & Robert Wiener

同步(synchronous):当程序1调用程序2时,程序1 停下不动,直到程序2完成回到程序1来,程序1才继续下去;

  • SendMessage() 根本就像是“直接调用窗口之窗口函数”,除非等该窗口函数结束,是不会回到原调用点的,所以它是同步行为。

异步(asynchronous):如果程序1调用程序2后,径自继续自己的下一个动作,那么两者之间就是asynchronous;

  • PostMessage() 是把消息放到对方的消息队列中,然后不管三七二十一,就回到原调用点继续执行,所以这是异步行为。
  1. Win32 中关于进程和线程的协调工作是由同步机制(synchronous mechanism)来完成的。
  2. 在任何关于同步机制的讨论中,不论是在 Win32 或 Unix 或其他操作系统,你一定会一再地听到这样一条规则:不要长时间锁住一份资源。最牢靠而最立即的警告:千万不要在一个 critical section 之中调用 Sleep() 或任何 Wait…() API 函数
    某些人会关心这样的问题:如果我再也不释放资源(或不离开 critical section,或不释放 mutex……等等),会怎样?答案是:不会怎样!操作系统不会当掉。用户不会获得任何错误信息。最坏的情况是,当主线程(一个 GUI 线程)需要使用这被锁定的资源时,程序会挂在那儿,动也不动。

1. 关键区域、临界区域(Critical Sections)

Critical section 并不是核心对象。 因此,没有所谓 handle 这样的东西。它和核心对象不同,它存在于进程的内存空间中。
一旦线程进入一个 critical section,它就能够一再地重复进入该 critical section。唯一的警告就是,每一个“进入”操作都必须有一个对应的“离开”操 作 。 如果某个线程调用EnterCriticalSection() 5 次,它也必须调用LeaveCriticalSection() 5 次,该 critical section 才能够被释放。
Critical section 的一个缺点就是,没有办法获知进入 critical section 中的那个线程是生是死。从另一个角度看,由于 critical section 不是核心对象,如果进入 critical section 的那个线程结束了或当掉了,而没有调用LeaveCriticalSection() 的话,系统没有办法将该 critical section 清除。如果你需要那样的机能,你应该使用 mutex。
任何时候当一段代码需要两个(或更多)资源时,都有潜在性的死锁阴影。强迫将资源锁定,使它们成为 ” all-or-nothing”(要不统统获得,要不统统没有),可以阻止死锁的发生,这样做往往会影响程序性能。更好的方式是使用下文的Mutex和WaitForMultipleObjects解决死锁问题。

#include <stdio.h>#include <Windows.h>#define LOOPNUM 10000000typedef struct tagSAFTDATA {    int sum;    CRITICAL_SECTION lock;} SAFTDATA;static SAFTDATA g_SaftData = {0};static void AddSafety() {    EnterCriticalSection(&g_SaftData.lock);    g_SaftData.sum += 1;    LeaveCriticalSection(&g_SaftData.lock);}static void LessSafety() {    EnterCriticalSection(&g_SaftData.lock);    g_SaftData.sum -= 1;    LeaveCriticalSection(&g_SaftData.lock);}DWORD WINAPI Thread(void *arg) {    for (int i = 0; i < LOOPNUM; i++) {        EnterCriticalSection(&g_SaftData.lock);        LessSafety();        g_SaftData.sum -= 1;        LeaveCriticalSection(&g_SaftData.lock);    }    return 0;}int main(void) {    DWORD start = GetTickCount();    InitializeCriticalSection(&g_SaftData.lock);    HANDLE hThread = CreateThread(NULL, 0, Thread, NULL, 0, NULL);     for (int i = 0; i < LOOPNUM; i++) {        AddSafety();    }    WaitForSingleObject(hThread, INFINITE);    CloseHandle(hThread);    DeleteCriticalSection(&g_SaftData.lock);    DWORD end = GetTickCount();    printf("%d main, usedtime=%d\n", g_SaftData.sum, end-start);    return 0;}

result

2. 互斥器(Mutexes)

虽然 mutex 和 critical section 做相同的事情,但是它们的运作还是有差别的:

  • 锁住一个未被拥有的 mutex,比锁住一个未被拥有的 critical section,需要花费几乎 100 倍的时间。因为 critical section 不需要进入操作系统核心。
  • Mutexes 可以跨进程使用。 Critical section 则只能够在同一个进程中使用。为了能够跨进程使用同一个 mutex,你可以在产生mutex 时指定其名称。如果你指定了名称,系统中的其他任何线程就可以使用这个名称来处理该 mutex。

Mutexes行为特征:

  • Mutex 是一个核心对象,因此它被保持在系统核心之中,并且和其他核心对象一样,有所谓的引用计数(reference count)。每次你调用CloseHandle(),引用计数便减 1。当引用计数达到 0 时, mutex 便自动被系统清除掉。如果拥有某 mutex 之线程结束了,该 mutex 会被自动清除的唯一情况是:此线程是最后一个与该 mutex handle 有关联的线程。 否则此核心对象的引用计数仍然是比 0 大,其他线程(以及进程)仍然可以拥有此 mutex 的合法handle。
  • 一旦没有任何线程拥有 mutex 且有一个线程正以 Wait…() 等待该 mutex,该 mutex 就会短暂地出现激发状态,使 Wait…() 得以返回
  • 第二个容易引人迷惑的地方是,Mutex 的拥有权并非属于那个产生它的线程,而是那个最后对此 mutex 进行 Wait…() 操作并且尚未进行 ReleaseMutex() 操作的线程。线程拥有 mutex 就好像线程进入 critical section 一样。一次只能有一个线程拥有该 mutex。
  • 有时候,线程可能没有在结束前调用ReleaseMutex()。为了解决这个问题,mutex 有一个在各种同步机制中是独一无二的特性:如果线程拥有一个 mutex 而在结束前没有调用ReleaseMutex(),mutex 不会被摧毁。取而代之的是,该 mutex 会被视为“ 未被拥有”以及“未被激发”,而下一个等待中的线程会被以WAIT_ABANDONED_0 通知。不论线程是因为ExitThread() 而结束,或是因当掉而结束,这种情况都存在。
  • 如果其他线程正以 WaitForMultipleObjects() 等待此 mutex,该函数也会返回,传回值介于 WAIT_ ABANDONED_0 和(WAIT_ABANDONED_0_n +1)之间,其中的 n 是指 handle 数组的元素个数。线程可以根据这个值了解到究竟哪一个mutex 被放弃了。至于 WaitForSingleObject(),则只是传回WAIT_ABANDONED_0。

(示例:Mutex同步进程间访问共享内存)

#include <stdio.h>#include <Windows.h>#define SAFTDATA_MUTEX "SAFTDATA_MUTEX"#define SAFTDATA_FILEMAP "SAFTDATA_FILEMAP"#define SAFTDATA_FILEMAP_SIZE sizeof(int)#define LOOPNUM 1000000typedef struct tagSAFTDATA {    int sum;    LPVOID filemapBuf;    HANDLE filemap;    HANDLE mutex;} SAFTDATA;static SAFTDATA g_SaftData = {0};#if 0// serverint main(void) {    DWORD start = GetTickCount();    g_SaftData.mutex = CreateMutex(NULL, FALSE, SAFTDATA_MUTEX); // 如果mutex名称已经存在,GetLastError()会传回ERROR_ALREADY_EXISTS    g_SaftData.filemap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, SAFTDATA_FILEMAP_SIZE, SAFTDATA_FILEMAP);    printf("[server] %#p(mutex), %#p(filemap)\n", g_SaftData.mutex, g_SaftData.filemap);    g_SaftData.filemapBuf = MapViewOfFile(g_SaftData.filemap, FILE_MAP_ALL_ACCESS, 0, 0, 0);    memset(g_SaftData.filemapBuf, 0, SAFTDATA_FILEMAP_SIZE);    UnmapViewOfFile(g_SaftData.filemapBuf);    for (int i = 0; i < LOOPNUM; i++) {        WaitForSingleObject(g_SaftData.mutex, INFINITE);        g_SaftData.filemapBuf = MapViewOfFile(g_SaftData.filemap, FILE_MAP_ALL_ACCESS, 0, 0, 0);        memcpy(&g_SaftData.sum, g_SaftData.filemapBuf, SAFTDATA_FILEMAP_SIZE);        g_SaftData.sum += 1;        memcpy(g_SaftData.filemapBuf, &g_SaftData.sum, SAFTDATA_FILEMAP_SIZE);        UnmapViewOfFile(g_SaftData.filemapBuf);        ReleaseMutex(g_SaftData.mutex);        //printf("%d ", i);    }    CloseHandle(g_SaftData.filemap);    CloseHandle(g_SaftData.mutex);    DWORD end = GetTickCount();    printf("[server] %d main, usedtime=%d, %#p(mutex), %#p(filemap)\n", g_SaftData.sum, end-start, g_SaftData.mutex, g_SaftData.filemap);    system("pause");    return 0;}#else// clientint main(void) {    DWORD start = GetTickCount();    g_SaftData.mutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, SAFTDATA_MUTEX);    g_SaftData.filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, SAFTDATA_FILEMAP);    printf("[client] %#p(mutex), %#p(filemap)\n", g_SaftData.mutex, g_SaftData.filemap);    for (int i = 0; i < LOOPNUM; i++) {        WaitForSingleObject(g_SaftData.mutex, INFINITE);        g_SaftData.filemapBuf = MapViewOfFile(g_SaftData.filemap, FILE_MAP_ALL_ACCESS, 0, 0, 0);        memcpy(&g_SaftData.sum, g_SaftData.filemapBuf, SAFTDATA_FILEMAP_SIZE);        g_SaftData.sum -= 1;        memcpy(g_SaftData.filemapBuf, &g_SaftData.sum, SAFTDATA_FILEMAP_SIZE);        UnmapViewOfFile(g_SaftData.filemapBuf);        ReleaseMutex(g_SaftData.mutex);        //printf("%d ", i);    }    CloseHandle(g_SaftData.filemap);    CloseHandle(g_SaftData.mutex);    DWORD end = GetTickCount();    printf("[client] %d main, usedtime=%d, %#p(mutex), %#p(filemap)\n", g_SaftData.sum, end-start, g_SaftData.mutex, g_SaftData.filemap);    system("pause");    return 0;}#endif

result

3. 信号量(Semaphores)

semaphores(信号量)在电脑科学中它是最具历史的同步机制。它是解决各种 producer/consumer 问题的关键要素,这种问题会存有一个缓冲区,可能在同一时间内被读出数据或被写入数据。
在许多系统中, semaphores 常被使用,因为 mutexes 可能并不存在。在 Win32 中 semaphores 被使用的情况就少得多,因为 mutex 存在的缘故。理论可以证明, mutex 是 semaphore 的一种退化。如果你产生一个 semaphore 并令最大值为 1,那就是一个 mutex。Win32 中的一个 semaphore 可以被锁住最多 n 次,其中 n 是 semaphore 被产生时指定的,semaphores 不像 mutexes,它并没有所谓的“wait abandoned”状态可以被其他线程侦测到。
semaphore 的现值代表的意义是目前可用的资源数。如果现值为 5,就表示还有五个锁定动作可以成功。每当一个锁定动作成功, semaphore 的现值就会减 1。你可以使用任何一种 Wait…() 函数(例如 WaitForSingleO bject())要求锁定一个 semaphore。因此,如果 sem aphore 的现值不为 0, Wait…() 函数会立刻返回。
如果锁定成功,你也不会收到 semaphore 的拥有权,因为可以有一个以上的线程同时锁定一个 semaphore,在 semaphore 身上并没有拥有权的观念,一个线程可以反复调用 Wait…() 函数以产生新的锁定。这和 mutex绝不相同:拥有 mutex 的线程不论再调用多少次 Wait…() 函数,也不会被阻塞住。一旦 semaphore 的现值降到 0,就表示资源已经耗尽。此时,任何线程如果调用 Wait…() 函数,必然要等待,直到某个锁定被解除为止。
为了解除锁定,你必须调用 ReleaseSem aphore()。这个函数将 semaphore的现值增加一个定额,通常是 1,并传回 semaphore 的前一个现值。
(示例:信号量解决 producer/consumer 的问题)

#include <stdio.h>#include <queue>#include <Windows.h>using namespace std;//#define SEMAPHORE#ifdef SEMAPHORE#define QUEUE_SIZE 10static HANDLE g_hSemEmpty = NULL;static HANDLE g_hSemFull = NULL;#endif #define THREAD_NUM 5static HANDLE g_hEvent = NULL; static CRITICAL_SECTION g_queueLock;static queue<int> g_Queue;static int g_startItem = 0;static void RecordQueueSizeMax() {    char curDir[256], confPath[256];    GetCurrentDirectory(sizeof(curDir), curDir);    sprintf(confPath, "%s\\record.txt", curDir);    int size = GetPrivateProfileInt("queue", "size", -1, confPath);    EnterCriticalSection(&g_queueLock);    //printf("---%d---\n", g_Queue.size());    int queuesize = g_Queue.size();    LeaveCriticalSection(&g_queueLock);    if (size < queuesize) {        char sizebuf[16];        sprintf(sizebuf, "%d", queuesize);        WritePrivateProfileString("queue", "size", sizebuf, confPath);          }}DWORD WINAPI ProducerThread(void *arg) {    int startItem = 0;    while (1) {        if (WAIT_TIMEOUT != WaitForSingleObject(g_hEvent, 0))            break; // 激发Event对象后退出线程        RecordQueueSizeMax(); // 记录queue队列中最大数量#ifdef SEMAPHORE        WaitForSingleObject(g_hSemEmpty, INFINITE); // g_hSemEmpty--#endif        EnterCriticalSection(&g_queueLock);        g_Queue.push(g_startItem++);        printf("+%d ", g_Queue.back());        LeaveCriticalSection(&g_queueLock);#ifdef SEMAPHORE        ReleaseSemaphore(g_hSemFull, 1, NULL); // g_hSemFull++#endif    }    return 0;}DWORD WINAPI ConsumerThread(void *arg) {    while (1) {        if (WAIT_TIMEOUT != WaitForSingleObject(g_hEvent, 0))            break; // 激发Event对象后退出线程#ifdef SEMAPHORE        WaitForSingleObject(g_hSemFull, INFINITE); // g_hSemFull--#endif        EnterCriticalSection(&g_queueLock);#ifndef SEMAPHORE        if (0 < g_Queue.size()) // 使用信号量时,无需此判断#endif        {            printf("-%d ", g_Queue.front());            g_Queue.pop();          }        LeaveCriticalSection(&g_queueLock);#ifdef SEMAPHORE        ReleaseSemaphore(g_hSemEmpty, 1, NULL); // g_hSemEmpty++#endif    }    return 0;}int main(void) {    DWORD start, end;    HANDLE hThreadArray[THREAD_NUM];    start = GetTickCount();    InitializeCriticalSection(&g_queueLock); // queue读写同步    g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // 手动控制、初始为非激发状态#ifdef SEMAPHORE    g_hSemEmpty = CreateSemaphore(NULL, QUEUE_SIZE, QUEUE_SIZE, NULL); // queue大小为QUEUE_SIZE是,生产者不能继续增加数据到queue     g_hSemFull = CreateSemaphore(NULL, 0, QUEUE_SIZE, NULL);  // queue大小为0是,消费者不能继续从queue中取出数据#endif    for (int i = 0; i < THREAD_NUM; i++) {        if (0 == i%2)            hThreadArray[i] = CreateThread(NULL, 0, ProducerThread, NULL, 0, NULL);        else            hThreadArray[i] = CreateThread(NULL, 0, ConsumerThread, NULL, 0, NULL);    }    { // 5s后激发Event对象,用于退出线程        Sleep(5000);        SetEvent(g_hEvent);     }    WaitForMultipleObjects(THREAD_NUM, hThreadArray, TRUE, INFINITE); // 等待所有线程    for (int i = 0; i < THREAD_NUM; i++)        CloseHandle(hThreadArray[i]);#ifdef SEMAPHORE    CloseHandle(g_hSemEmpty);    CloseHandle(g_hSemFull);#endif    CloseHandle(g_hEvent);    DeleteCriticalSection(&g_queueLock);    end = GetTickCount();    printf("elapseMSec=%lu, queueSize=%d\n", end-start, g_Queue.size());    system("pause");    return 0;}

(不使用信号量时)
no
(使用信号量时)
yes

4. 事件(Event Objects)

Win32 中最具弹性的同步机制就属 events 对象了。 Event 对象是一种核心对象,它的唯一目的就是成为激发状态或未激发状态。这两种状态全由程序来控制,不会成为 Wait…() 函数的副作用
示例参考: Win32线程——在某个线程内终止另一个正在运行的线程(2)(Event对象)

5. Interlocked Variables

同步机制的最简单类型是使用 interlocked 函数,对着标准的 32 位变量进行操作。这些函数并没有提供“等待”机能,它们只是保证对某个特定变量的存取操作是“一个一个接顺序来”。
Interlocked适合去维护一个 32 位计数器的“排他性存取”性质,由于一个 32 位变量的存取操作只需要 2~3 个机器指令,这时候使用一个 critical section 或一个 mutex 太浪费时间和资源。

#include <stdio.h>#include <Windows.h>#define OPERAT_COUNT 10000000static int g_RefCount = 0;static volatile int g_RefCount_s = 0;DWORD WINAPI AddThread(void *arg) {    for (int i = 0; i < OPERAT_COUNT; i++)        g_RefCount++;    return 0;}DWORD WINAPI LessThread(void *arg) {    for (int i = 0; i < OPERAT_COUNT; i++)        g_RefCount--;    return 0;}DWORD WINAPI AddThread_s(void *arg) {    for (int i = 0; i < OPERAT_COUNT; i++)        InterlockedIncrement((LONG *)&g_RefCount_s);    return 0;}DWORD WINAPI LessThread_s(void *arg) {    for (int i = 0; i < OPERAT_COUNT; i++)        InterlockedDecrement((LONG *)&g_RefCount_s);    return 0;}int main(void) {    DWORD start, end;    HANDLE hThreadArray[2];    start = GetTickCount();    hThreadArray[0] = CreateThread(NULL, 0, AddThread, NULL, 0, NULL);    hThreadArray[1] = CreateThread(NULL, 0, LessThread, NULL, 0, NULL);    WaitForMultipleObjects(2, hThreadArray, TRUE, INFINITE); // 等待所有线程    CloseHandle(hThreadArray[0]);    CloseHandle(hThreadArray[1]);    end = GetTickCount();    printf("g_RefCount=%d, elapseMSec=%lu\n", g_RefCount, end-start);    start = GetTickCount();    hThreadArray[0] = CreateThread(NULL, 0, AddThread_s, NULL, 0, NULL);    hThreadArray[1] = CreateThread(NULL, 0, LessThread_s, NULL, 0, NULL);    WaitForMultipleObjects(2, hThreadArray, TRUE, INFINITE); // 等待所有线程    CloseHandle(hThreadArray[0]);    CloseHandle(hThreadArray[1]);    end = GetTickCount();    printf("g_RefCount_s=%d, elapseMSec=%lu\n", g_RefCount_s, end-start);    return 0;}

interlocked

(示例:Interlocked…() 函数作为一种同步机制被使用于 spin-lock)

#include <stdio.h>#include <Windows.h>static volatile BOOL g_spinFlag = FALSE;DWORD WINAPI Thread(void *arg) {    while(TRUE == InterlockedExchange((LONG *)&g_spinFlag, TRUE)) {        Sleep(100); // 进入自旋锁,等待    }    for(int i = 0; i < 5; i++) {        printf("%d ", *(int *)arg);        Sleep(1000);    }    InterlockedExchange((LONG *)&g_spinFlag, FALSE); // 释放自旋锁    return 0;}int main(void) {    DWORD start, end;    int arg[2] = {1, 2};    HANDLE hThreadArray[2];    start = GetTickCount();    hThreadArray[0] = CreateThread(NULL, 0, Thread, &arg[0], 0, NULL);    hThreadArray[1] = CreateThread(NULL, 0, Thread, &arg[1], 0, NULL);    WaitForMultipleObjects(2, hThreadArray, TRUE, INFINITE); // 等待所有线程    CloseHandle(hThreadArray[0]);    CloseHandle(hThreadArray[1]);    end = GetTickCount();    printf("elapseMSec=%lu\n", end-start);    return 0;}

spinlock

0 0
原创粉丝点击