您的位置:首页 > 其它

Win32 线程知识点梳理二

2015-09-15 22:13 246 查看
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多线程程序设计
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: