Win32 线程知识点梳理二

Win32中sendMessage() 是同步行为,而PostMessage()是异步行为。关于进程和线程的协调工作是由同步机制来完成的。

Critical Section 临界区

它的含义是指一小块用来处理一份被共享的资源的程序代码,这里的资源指的是广义的如一块内存、一个数据结构、一个文件或其他具有使用排他性的的东西。对资源的保护是通过允许一次仅仅一个线程进入critical section。
注意:Critical Section不是核心对象,没有所谓的handle这样的东西,它存在于进程的内存空间,你不需要使用Create这样的API函数来获得一个critical section handle。

你应该做的是对一个类型为CRITICAL_SECTION的变量初始化。调用InitializeCriticalSection

void WINAPI InitializeCriticalSection(
  _Out_ LPCRITICAL_SECTION lpCriticalSection
);

参数1:1个指针,指向欲被初始化的critical_section变量

void WINAPI DeleteCriticalSection(
  _Inout_ LPCRITICAL_SECTION lpCriticalSection
);

参数1:指向一个不再需要的CRITICAL_SECTION变量

void WINAPI EnterCriticalSection(
  _Inout_ LPCRITICAL_SECTION lpCriticalSection
);

参数1:指向一个即将锁定的变量
当线程即将进入其中,必须通过这一关。

void WINAPI LeaveCriticalSection(
  _Inout_ LPCRITICAL_SECTION lpCriticalSection
);

参数1: 指向一个即将解除锁定的变量

警告:不要在一个critical section之中调用sleep()或任何Wait…()API函数
由于critical section不是核心对象,如果进入critical section的那个线程挂掉而没有调用LeaveCriticalSection()的话,系统没有办法将该critical section清除。如果需要这样的机能,应该使用mutex。

DeadLock/ The Deadly Embrace

当双方都握有对方所需要的东西,这种情况称为死锁。

 void SwapLists(List* list, List* list2){
    List * tmp_list;
    EnterCriticalSection(list1->critical_sec);
    EnterCriticalSection(list2->critical_sec);
    tmp->list = list1->head;
    list1->head = list2->head;
    list2->head = tmp->list;
    LeaveCriticalSection(list1->critical_sec);
    LeaveCriticalSection(list2->critical_sec);
}

Mutex

首先谈下Mutex和CriticalSection的区别:

  • 锁住一个未被拥有的mutex,比锁住一个未被拥有的critical section需要花费几乎100倍的时间,因为 critical
  • section只是在user mode下就可以完成。 Mutexes可以跨进程使用,Critical section只能在同一个进程中使用
  • 等待一个Mutex时,你可以指定结束等待的时间长度,而等待一个Critical Section不行。

产生一个互斥器

HANDLE WINAPI CreateMutex(
  _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes,
  _In_     BOOL                  bInitialOwner,
  _In_opt_ LPCTSTR               lpName
);

参数1:安全属性,NULL表示使用默认属性
参数2:调用CreateMutex这个函数的线程同时拥有mutex,该值设定为true
参数3:mutex的名称,名称是字符串,任何进程和线程都可以使用这个名称
返回值: 成功返回一个handle,否则返回NULL,可以通过调用GetLastError()来获得失败信息,如,指定的mutex名称已经存在,则会传回ERROR_ALREADY_EXISTS

打开一个互斥器

如果一个mutex已经产生并有一个名称,那么其它的进程和线程就可以根据该名称打开那个mutex。

HANDLE WINAPI OpenMutex(
  _In_ DWORD   dwDesiredAccess,
  _In_ BOOL    bInheritHandle,
  _In_ LPCTSTR lpName
);

参数2:If this value is TRUE, processes created by this process will inherit the handle. Otherwise, the processes do not inherit this handle.

关于激发态:

一旦没有任何线程拥有mutex,并且有一个线程以Wait…()等待该mutex,该mutex就会短暂出现激发状态,使得Wait…()得以返回,随后Mutex又会立即变为非激发状态,知道获得该Mutex的线程调用ReleaseMutex()将该mutex释放掉。

被舍弃的Mutex

如果一个线程拥有一个mutex,在结束前却没有调用ReleaseMutex,mutex不会被摧毁,而下一个等待的线程会被以WAIT_ABANDONED_0通知,不论线性是因为ExitThread()而结束或是因为挂掉而结束,这种情况都会存在。
如果其他线程以WaitForMultipleObjects()等待此mutex,该函数也会返回,传回值介于:
WAIT_ABANDONED_0 ~ WAIT_ABANDONED_0+N-1

关于死锁

在哲学家进餐问题中,他们不愿意在吃完之前放下他们的筷子,但是一定要一双筷子才可以开始吃,如果允许哲学家一次取得一只筷子,那么就有可能每个哲学家都抓住了左手的筷子,这时他们就不可能抓住右手的筷子,因为右手边的哲学家拒绝让出。
如果我们修改程序,让哲学家一次等待两只筷子,程序代码像这样:

WaitForMultipleObjects(2,myChopsticks,TRUE,INFINITE);

也就是哲学家只有在有一对筷子时才会拿起筷子,死锁就不会发生。

修正上面的代码

  void swapLists(struct List * list ,struct List * list2){
    struct List * tmp_list;
    HANDLE arrhandles[2];
    arrhandles[0] = list1->hMutex;
    arrhandles[1] = list2->hMutex;
    WaitForMultipleObjects(2,arrhandles,TRUE,INFINITE);
    tmp->list = list1->head;
    list1->head = list2->head;
    ReleaseMutex(arrhandles[0]);
    ReleaseMutex(arrhandles[1]);
    }

Semaphores

mutex是semaphore的一种退化,如果你产生一个semaphore,并令最大值为1,那就是一个mutex,mutex因此又被称为binary semaphore。

产生信号量

HANDLE WINAPI CreateSemaphore(
  _In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
  _In_     LONG                  lInitialCount,
  _In_     LONG                  lMaximumCount,
  _In_opt_ LPCTSTR               lpName
);

参数1:安全属性
参数2:semaphore初值,必须大于或等于0,小于等于lMaximumCount
参数3:Semaphore最大值,也就是同一个时间能够锁住Semaphore的最多个数
参数4: Semaphore名称,任何线程都可以根据这一名称引用到这个Semaphore, NULL表示没有名字

获得锁定

每当一个锁定动作(调用Wait…())成功,semaphore现值就会减少1,如果锁定成功你也不会拥有它的所有权。

解除锁定

BOOL WINAPI ReleaseSemaphore(
  _In_      HANDLE hSemaphore,
  _In_      LONG   lReleaseCount,
  _Out_opt_ LPLONG lpPreviousCount
);

参数1:Semaphore的handle
参数2:Semaphore现值的增额,不可以为负值或0
参数3:传回Semaphore原来的现值,是一个瞬间值,不可以把IReleaseCount+*lpPreviousCount作为semaphore的现值,因为别的线程可能已经改变了semaphore的值。

事件(Event Objects)

Win32中最具弹性的同步机制就是events对象,Event对象是一种核心对象,它通过程序来控制成为激发状态和未激发状态。
Event对象被应用在多种类型的高级I/O操作中。

产生一个event对象

HANDLE WINAPI CreateEvent(
  _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
  _In_     BOOL                  bManualReset,
  _In_     BOOL                  bInitialState,
  _In_opt_ LPCTSTR               lpName
);

参数1:安全属性
参数2:如果为FALSE,表示这个event将在变为激发态之后自动变为非激发态,如果为True表示不会自动重置,必须依靠程序调用ResetEvent()才能变为非激发态
参数3:如果为true,表示这个event一开始处于激发态,如果为false,表示一开始处于非激发态
参数4:event对象的名字

BOOL WINAPI SetEvent(
  _In_ HANDLE hEvent
);

把对象设置为激发状态

BOOL WINAPI ResetEvent(
  _In_ HANDLE hEvent
);

把对象设定为非激发状态

BOOL WINAPI PulseEvent(
  _In_ HANDLE hEvent
);

如果是一个manual reset event,把event对象设为激发状态,唤醒所有等待中的线程,然后把event恢复为非激发状态,如果是一个auto reset event,把event对象设为激发状态,唤醒一个等待中的线程,然后event恢复为非激发状态。

如果对一个autoReset event对象调用setEvent()或PulseEvent(),而彼时没有任何线程在等待,event会被遗失,换句话说,除非有线程正在等待,否则event不会被保存下来。

这里就可能存在着一种死锁的情况:
receiver线程检查队列是否有字符,这时发生context switch,切换到sender线程,它对一个event对象进行pulse操作,这时候又发生了context switch,回到receiver线程,调用waitForSingleObject(),等待event对象,由于这个动作发生在sender线程激发event之后,所以event会遗失—— 所以receiver永远不会醒来,程序就进入了死锁的状态。

Interlocked Variables

这是同步机制最简单的类型,对标准的32位变量进行操作,它们只保证对某个特定变量的存取操作是一个个按顺序来的。
其实是一个原子操作,如:

LONG __cdecl InterlockedIncrement(
  _Inout_ LONG volatile *Addend
);

Decrements (decreases by one) the value of the specified 32-bit variable as an atomic operation.

LONG __cdecl InterlockedDecrement(
  _Inout_ LONG volatile *Addend
);

参数1:32位变量的地址,这个变量被递减,然后将结果与0比较,这个地址必须指向long word
如果等于0,返回0,大于0传回正值,小于0传回负值。

LONG __cdecl InterlockedExchange(
  _Inout_ LONG volatile *Target,
  _In_    LONG          Value
);

interlocked..()如果被用于spin-lock(又称为自旋锁,线程通过busy-wait-loop的方式来获取锁,任何时刻时刻只有一个线程能够获得锁,其他线程忙等待直到获得锁,spinlock不会导致线程的状态切换)那么它只是一种同步机制。
它主要用于引用计数,可以不需要用到critical section或mutex之类,毕竟一个32位变量的存取操作只需要2~3个机器指令而已。


资料来源:
Win32多线程程序设计

你可能感兴趣的:(多线程)