Windows Via C/C++ 读书笔记 6

Windows Via C/C++ 读书笔记 

1. 用内核对象做线程同步

1.1. Overview

Why use kernel object

内核对象做同步操作要进入系统模式,比用户模式更消耗CPU,为什么还要用呢?

内核对象同步操作功能更多,比如跨进程的同步,非阻塞同步等待。

内核对象同步的关键

内核对象有两个状态:"signaled", "nonsignaled"。编程的时候可以"wait"一个内核对象到"signaled"状态才执行。

具有这两种状态的内核对象有:

1. Processes

2. Threads

3. Jobs

4. File and console standard input/output/error streams

5. Events

6. Waitable timers

7. Semaphores

8. Mutexes

1.2. Wait函数

前面提到可以"wait"内核对象到"signaled"状态。有2个函数支持这个操作:

DWORD WaitForSingleObject(

   HANDLE hObject,

   DWORD dwMilliseconds);

时间参数为"INFINITE"表示不超时,一直等待。

返回值可以表示等待超时(WAIT_TIMEOUT),还是成功(WAIT_OBJECT_0),或者异常(WAIT_FAILED)。

DWORD WaitForMultipleObjects(

   DWORD dwCount,

   CONST HANDLE* phObjects,

   BOOL bWaitAll,

   DWORD dwMilliseconds);

等待多个内核对象。如果把"bWaitAll"参数设为"false",表示任何一个内核对象变为"signaled",程序就返回,否则要等到所有的对象都"signaled"才返回。那么如何判断是哪个内核对象造成返回呢,可以通过返回结果判断:WAIT_OBJECT_0 + N,表示第N个对象,N0开始。

case WAIT_OBJECT_0 + 0:

      // The process identified by h[0] (hProcess1) terminated.

      break;

1.3. Wait成功的副作用

Wait函数成功返回前,会把内核对象的状态由"signaled"变为"nonsignaled"。而失败返回是不会做修改操作的,失败包括超时和异常。

当然,不是所有的内核对象都有这种副作用,比如:ProcessThread。它们在结束的时候变为"signaled",而且永远不会再变回来。

"WaitForMultipleObjects"操作是原子的,否则可能会死锁。

如果不是原子的会怎么样呢?比如有2个线程等待2个对象,对象1变为signaled,线程1把它置为"nonsignaled"。对象2变为"signaled",而线程2先发现,并且把它置为"nosignaled"。死锁发生了,线程1在等待对象2,线程2在等待对象1

为了避免这种死锁,"WaitForMultipleObjects"是会把所有对象锁定住,不让其它线程修改。

等待策略

多个线程等待一个对象,谁会先得到通知呢?答案是操作系统不做任何调度,没有优先级考虑,因此就看各个线程的造化了。

1.4. Event内核对象

事件内核对象分"auto-reset" "manual-reset"两种。Auto表示wait操作返回前会把对象reset"nonsignaled"(见前文 副作用),因此只有一个线程能执行。Manul则会唤醒多个线程执行。

线程可以通过名称访问同一个事件对象,也可以通过前面讲的内核对象句柄的inheritanceDuplicateHandle全局变量则是另一种最简单的办法。

1.5. Waitable Timer 

Timer对象有两种用法

1. Timer对象会在某个时间后,每隔一定时间变成"signaled"状态。

2. 可以设置一个触发执行函数,当时间到了时候,调用设置函数的线程会执行这个函数。

它的类型也有两种,auto-setmanua-set,跟Event作用相同。

不想讲太多api,可以查msdn

Tips

1.创建timer后,timer自动为"nonsignaled"

2.设置的时间是绝对时间的话,是允许这个时间已经过去了。比如调用时刻是530分,是可以把起始时间设为430分的。

3.如果设置参数起始时间为负,表示调用设置函数那个时间过一定时间开始计时。比如传入一个-t时间,T时刻调用函数。那么计时起始时间是t+T。不明白就看msdn吧。

2个例子:

绝对时间:

// Declare our local variables.

HANDLE hTimer;

SYSTEMTIME st;

FILETIME ftLocal, ftUTC;

LARGE_INTEGER liUTC;

// Create an auto-reset timer.

hTimer = CreateWaitableTimer(NULL, FALSE, NULL);

// First signaling is at January 1, 2008, at 1:00 P.M. (local time).

st.wYear         = 2008; // Year

st.wMonth        = 1;    // January

st.wDayOfWeek    = 0;    // Ignored

st.wDay          = 1;    // The first of the month

st.wHour         = 13;   // 1PM

st.wMinute       = 0;    // 0 minutes into the hour

st.wSecond       = 0;    // 0 seconds into the minute

st.wMilliseconds = 0;    // 0 milliseconds into the second

SystemTimeToFileTime(&st, &ftLocal);

// Convert local time to UTC time.

LocalFileTimeToFileTime(&ftLocal, &ftUTC);

// Convert FILETIME to LARGE_INTEGER because of different alignment.

liUTC.LowPart  = ftUTC.dwLowDateTime;

liUTC.HighPart = ftUTC.dwHighDateTime;

// Set the timer.

SetWaitableTimer(hTimer, &liUTC, 6 * 60 * 60 * 1000,

   NULL, NULL, FALSE); ...

调用函数的时刻过相对时间开始计时:

// Declare our local variables.

HANDLE hTimer;

LARGE_INTEGER li;

// Create an auto-reset timer.

hTimer = CreateWaitableTimer(NULL, FALSE, NULL);

// Set the timer to go off 5 seconds after calling SetWaitableTimer.

// Timer unit is 100 nanoseconds.

const int nTimerUnitsPerSecond = 10000000;

// Negate the time so that SetWaitableTimer knows we

// want relative time instead of absolute time.

li.QuadPart = -(5 * nTimerUnitsPerSecond);

// Set the timer.

SetWaitableTimer(hTimer, &li, 6 * 60 * 60 * 1000,

   NULL, NULL, FALSE); ...

1.6. Semaphore 信号量内核对象

信号量,操作系统讲很多了。PV操作,减少资源,增加资源。当资源数量大于1,允许线程进入,并消耗一个资源。

没有任何办法在不改变信号量资源数目的前提下获取当前信号量的可用资源数量。

1.7. Mutex互斥体内核对象

Mutex可以说是用得最多的同步对象了。作用跟critical section类似。

Mutex3个重要属性:

1. 引用计数

内核对象引用计数,表示有多少个地方打开了这个对象。

2. 线程id

如果id=0,表示没有任何线程占用。否则为占用线程的id。操作系统就是通过id是否为0判断是否有线程占用。

3. 递归次数

表示这个线程调用了几次wait函数获取这个对象。比如函数递归调用中,函数A需要获取mutexA,函数B也需要获取mutexA。如果函数A在获取mutexA后又调用了函数B,递归计数会加一。如果不这样,会死锁,即锁不能递归。

1.7.1. 线程非正常退出WAIT_ABANDONED

如果线程在释放mutex前非正常退出。操作系统会发现这种情况,然后把mutex的线程id设为0,递归次数设为0,再唤醒一个等待的线程。唯一不同的是,wait函数的返回值不是WAIT_OBJECT_0,而是WAIT_ABANDONED

1.7.2. Mutex比较critical section

Characteristic

Mutex

Critical Section

Performance

Slow

Fast

Can be used across process boundaries

Yes

No

Declaration

HANDLE hmtx;

CRITICAL_SECTION cs;

Initialization

hmtx = CreateMutex (NULL, FALSE, NULL);

InitializeCriticalSection(&cs);

Cleanup

CloseHandle(hmtx);

DeleteCriticalSection(&cs);

Infinite wait

WaitForSingleObject (hmtx, INFINITE);

EnterCriticalSection(&cs);

0 wait

WaitForSingleObject (hmtx, 0);

TryEnterCriticalSection(&cs);

Arbitrary wait

WaitForSingleObject (hmtx, dwMilliseconds);

Not possible

Release

ReleaseMutex(hmtx);

LeaveCriticalSection(&cs);

Can be waited on with other kernel objects

Yes (use WaitForMultipleObjects or similar function)

No

1.8. 内核对象手册

Object

When Nonsignaled

When Signaled

Successful Wait Side Effect

Process

While process is still active

When process terminates (Exit-ProcessTerminateProcess)

None

Thread

While thread is still active

When thread terminates (Exit-ThreadTerminateThread)

None

Job

When job's time has not expired

When job time expires

None

File

When I/O request is pending

When I/O request completes

None

Console input

No input exists

When input is available

None

File change notifications

No files have changed

When file system detects changes

Resets notification

Auto-reset event

ResetEventPulseEvent, or successful wait

When SetEvent/PulseEventis called

Resets event

Manual-reset event

ResetEvent or PulseEvent

When SetEvent/PulseEventis called

None

Auto-reset waitable timer

CancelWaitableTimer or successful wait

When time comes due (SetWaitableTimer)

Resets timer

Manual-reset waitable timer

CancelWaitableTimer

When time comes due (SetWaitableTimer)

None

Semaphore

Successful wait

When count > 0 (ReleaseSemaphore)

Decrements count by 15

Mutex

Successful wait

When unowned by a thread (ReleaseMutex)

Gives ownership to a thread

Critical section (user-mode)

Successful wait ((Try)Enter-Critical-Section)

When unowned by a thread (LeaveCriticalSection)

Gives ownership to a thread

SRWLock (user-mode)

Successful wait (Acquire-SRWLock(Exclusive))

When unowned by a thread (ReleaseSRWLock(Exclusive))

Gives ownership to a thread

Condition variable (user-mode)

Successful wait (SleepConditionVariable*)

When woken up (Wake(All)ConditionVariable)

None

1.9. 其它线程同步函数

异步IO

用异步IO,可以把读写工作交给操作系统后,继续执行其它工作。然后通过wait IO对象的"signaled"状态等待IO操作结束。

WaitForInputIdle

DWORD WaitForInputIdle(

   HANDLE hProcess,

   DWORD dwMilliseconds);

可以等待一个进程没有输入,例如,需要等待一个进程初始化窗口完毕后做一些工作,可以用这个函数。

MsgWaitForMultipleObjects(Ex)

WaitForDebugEvent

等待Debug事件,用于编写自己的debugger,太高级了,没用过。

SignalObjectAndWait

Signale一个对象,然后等待一个对象。整个操作是原子的。可以被signale的对象只能是mutex, semaphore, event

目的:

1.提高效率,如果分两步走,需要两次用户模式到系统模式的切换。

2.第二个原因有点极端。函数用在当一个线程不知道另一个线程什么时候会进入wait函数的时候。

书上举了一个例子:

2个线程,线程A通知线程B完成一定工作,然后wait线程B工作完成的通知。代码如下:

线程A

// Perform some work. ... SetEvent(hEventWorkerThreadDone);

WaitForSingleObject(hEventMoreWorkToBeDone, INFINITE);

// Do more work. ...

线程B

WaitForSingleObject(hEventWorkerThreadDone);

PulseEvent(hEventMoreWorkToBeDone);

PulseEvent表示signale一个事件后立即nonsignale一个事件。如果这个时候线程A不在wait状态,那这个事件就丢失了。

修改后的代码

线程A

// Perform some work. ... SignalObjectAndWait(hEventWorkerThreadDone,

   hEventMoreWorkToBeDone, INFINITE, FALSE);

// Do more work. ...

区别就是线程A通知B开始工作后立即进入wait状态。

Wait Chain TraversalWCT) API

Vista提供了跟踪锁状态的API,可以有效侦测到死锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值