Win32线程——同步机制

本文详细介绍了Win32线程的同步机制,包括关键区域、临界区域(Critical Sections)、互斥器(Mutexes)、信号量(Semaphores)和事件(Event Objects)。讨论了各种同步机制的特点、使用场景和注意事项,如避免死锁,以及Mutex的跨进程特性。同时提到了Interlocked Variables在简单同步操作中的应用。
摘要由CSDN通过智能技术生成

《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 10000000

typedef 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 通知
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值