《Windows核心编程》の线程间同步

Windows中线程间同步的方法主要有:事件(Event)、临界区(Critical Section)、互斥量(Mutex)和信号灯(Semaphore)。

1)使用事件对象进行线程间同步:

在使用CreateEvent函数创建事件对象时,将bManualReset参数设置为FALSE,然后在需要独占操作的代码前面加上一个WaitForSingleObject函数,后面加上一个SetEvent即可。

由于bManualReset参数为FALSE,这样当某个线程等待到Event后,Event对象的状态马上就变为复位状态,这样其他线程执行到WaitForSingleObject时就全部处于等待中了,当活动的线程操作完毕后,执行SetEvent函数,Event对象的状态才恢复到置位,这样其他等待的线程才会有一个能继续操作。

SetEvent函数原型如下:

HANDLE WINAPI CreateEvent(

__in_opt LPSECURITY_ATTRIBUTES lpEventAttributes, //安全描述符

__in BOOL bManualReset, //指定事件对象是否需要手动复位

__in BOOL bInitialState, //指定事件对象创建时的初始状态,为TRUE表示初始状态是置位状态;

//FALSE表示初始状态是复位状态

__in_opt LPCTSTR lpName //指定事件对象名称

);

当一个事件被创建后,程序就可以通过SetEventResetEvent函数来设置事件的状态:

BOOL WINAPI SetEvent(

__in HANDLE hEvent

);

BOOL WINAPI ResetEvent(

__in HANDLE hEvent

);

事件对象不仅可以用于一个进程中多线程同步,还可以用于多进程中的线程同步,只要在创建事件对象时指定事件名字,在其他进程中可以使用OpenEvent函数根据名称打开该事件对象进行使用:

HANDLE WINAPI OpenEvent(

__in DWORD dwDesiredAccess, //对事件对象的访问限制

__in BOOL bInheritHandle, //子进程是否继承该事件句柄

__in LPCTSTR lpName //事件对象的名字

);

实例代码如下:

#include <windows.h>

#include <stdio.h>

#define THREADCOUNT 4

HANDLE ghWriteEvent;

HANDLE ghThreads[THREADCOUNT];

DWORD WINAPI ThreadProc(LPVOID);

void CreateEventsAndThreads(void)

{

int i;

DWORD dwThreadID;

// Create a manual-reset event object. The write thread sets this

// object to the nonsignaled state when it finishes writing to a

// shared buffer.

ghWriteEvent = CreateEvent(

NULL, // default security attributes

TRUE, // manual-reset event

FALSE, // initial state is nonsignaled

TEXT("WriteEvent") // object name

);

if (ghWriteEvent == NULL)

{

printf("CreateEvent failed (%d)/n", GetLastError());

return;

}

// Create multiple threads to read from the buffer.

for(i = 0; i < THREADCOUNT; i++)

{

// TODO: More complex scenarios may require use of a parameter

// to the thread procedure, such as an event per thread to

// be used for synchronization.

ghThreads[i] = CreateThread(

NULL, // default security

0, // default stack size

ThreadProc, // name of the thread function

NULL, // no thread parameters

0, // default startup flags

&dwThreadID);

if (ghThreads[i] == NULL)

{

printf("CreateThread failed (%d)/n", GetLastError());

return;

}

}

}

void WriteToBuffer(VOID)

{

// TODO: Write to the shared buffer.

printf("Main thread writing to the shared buffer.../n");

// Set ghWriteEvent to signaled

if (! SetEvent(ghWriteEvent) )

{

printf("SetEvent failed (%d)/n", GetLastError());

return;

}

}

void CloseEvents()

{

// Close all event handles (currently, only one global handle).

CloseHandle(ghWriteEvent);

}

void main()

{

DWORD dwWaitResult;

// TODO: Create the shared buffer

// Create events and THREADCOUNT threads to read from the buffer

CreateEventsAndThreads();

// At this point, the reader threads have started and are most

// likely waiting for the global event to be signaled. However,

// it is safe to write to the buffer because the event is a

// manual-reset event.

WriteToBuffer();

printf("Main thread waiting for threads to exit.../n");

// The handle for each thread is signaled when the thread is

// terminated.

dwWaitResult = WaitForMultipleObjects(

THREADCOUNT, // number of handles in array

ghThreads, // array of thread handles

TRUE, // wait until all are signaled

INFINITE);

switch (dwWaitResult)

{

// All thread objects were signaled

case WAIT_OBJECT_0:

printf("All threads ended, cleaning up for application exit.../n");

break;

// An error occurred

default:

printf("WaitForMultipleObjects failed (%d)/n", GetLastError());

return;

}

// Close the events to clean up

CloseEvents();

}

DWORD WINAPI ThreadProc(LPVOID lpParam)

{

DWORD dwWaitResult;

printf("Thread %d waiting for write event.../n", GetCurrentThreadId());

dwWaitResult = WaitForSingleObject(

ghWriteEvent, // event handle

INFINITE); // indefinite wait

switch (dwWaitResult)

{

// Event object was signaled

case WAIT_OBJECT_0:

//

// TODO: Read from the shared buffer

//

printf("Thread %d reading from buffer/n",

GetCurrentThreadId());

break;

// An error occurred

default:

printf("Wait error (%d)/n", GetLastError());

return 0;

}

// Now that we are done reading the buffer, we could use another

// event to signal that this thread is no longer reading. This

// example simply uses the thread handle for synchronization (the

// handle is signaled when the thread terminates.)

printf("Thread %d exiting/n", GetCurrentThreadId());

return 1;

}

2)使用临界区对象进行线程间同步:

临界区对象(Critical Section)是定义在数据段中的一个CRITICAL_SECTION结构,它的维护和测试工作都是由Windows来完成的。结构应该定义成全局变量,因为在各线程中都有测试它。

定义了CRITICAL_SECTION结构后,必须首先对其进行初始化:

void WINAPI InitializeCriticalSection(

__out LPCRITICAL_SECTION lpCriticalSection

);

由于Windows系统保证临界区对象同时只能被一个线程进入,所以在需要独占操作的代码前加上进入临界区的操作,在代码后面加上离开临界区的操起,就可以保证操作的独占性了。

进入临界区的工作由函数EnterCriticalSection来完成:

void WINAPI EnterCriticalSection(

__inout LPCRITICAL_SECTION lpCriticalSection

);

如果当前其他线程拥有临界区,函数不会返回;反之如果函数返回就表示现在可以独占数据了。

当完成操作时,将临界区交还给Windows,以便其他线程可以申请使用,这个工作由函数LeaveCriticalSection来完成:

void WINAPI LeaveCriticalSection(

__inout LPCRITICAL_SECTION lpCriticalSection

);

最后,当程序不再需要临界区时,必须使用函数DeleteCriticalSection将它删除:

void WINAPI DeleteCriticalSection(

__inout LPCRITICAL_SECTION lpCriticalSection

);

与事件对象不同,由于临界区对象无法命名,所以无法跨进程使用,但正是因为事件对象可以跨进程使用,需要占用更多的资源,所以相比之下临界区对象在速度上的优势很明显。

使用临界区的缺点在于,如果某个线程进入临界区后挂掉了,那么将无法被其他等待的线程检测到,因为这些线程都被陷在EnterCriticalSection函数中了。而使用事件则不同,它可以在WaitForSingleObject函数中指定一个超时时间。

一般在对速度要求比较高,且不必跨进程进行同步的情况下,建议使用临界区对象。

实例代码片段如下:

// Global variable

CRITICAL_SECTION CriticalSection;

void main()

{

...

// Initialize the critical section one time only.

if (!InitializeCriticalSectionAndSpinCount(&CriticalSection,

0x80000400) )

return;

...

// Release resources used by the critical section object.

DeleteCriticalSection(&CriticalSection);

}

DWORD WINAPI ThreadProc( LPVOID lpParameter )

{

...

// Request ownership of the critical section.

EnterCriticalSection(&CriticalSection);

// Access the shared resource.

// Release ownership of the critical section.

LeaveCriticalSection(&CriticalSection);

...

}

3)使用互斥量对象进行线程间同步:

和临界区类似,互斥量也是一种只允许一个线程获取的对象,但它是可以命名的,因而是可以跨越进程使用的。

互斥量使用CreateMutex函数创建:

HANDLE WINAPI CreateMutex(

__in_opt LPSECURITY_ATTRIBUTES lpMutexAttributes, //安全描述符

__in BOOL bInitialOwner, //TRUE,表示创建它的线程直接获取了该互斥量;

// FALSE,表示互斥量创建后处于空闲状态

__in_opt LPCTSTR lpName //指定互斥对象的名字,可以使用OpenMutex来打开

);

释放互斥量使用函数ReleaseMutex,但是首先调用该函数的线程必须拥有该互斥量,这一点和使用Event不同,在任何线程中都可以使用SetEvent函数来将Event对象置位:

BOOL WINAPI ReleaseMutex(

__in HANDLE hMutex

);

互斥量是靠WaitForSingleObject函数来获取的,当互斥量被某个线程获取(即等待成功)后,它的状态是复位的,否则是置位的。

当不再使用互斥量时,可以使用CloseHandle来关闭它。

使用互斥量进行同步的缺点和使用事件对象类似,也是在效率上远远低于使用临界区;优点也是类似的,就是可以跨进程使用,可以检测到其他线程是否挂掉。

实例代码如下:

#include <windows.h>

#include <stdio.h>

#define THREADCOUNT 2

HANDLE ghMutex;

DWORD WINAPI WriteToDatabase( LPVOID );

void main()

{

HANDLE aThread[THREADCOUNT];

DWORD ThreadID;

int i;

// Create a mutex with no initial owner

ghMutex = CreateMutex(

NULL, // default security attributes

FALSE, // initially not owned

NULL); // unnamed mutex

if (ghMutex == NULL)

{

printf("CreateMutex error: %d/n", GetLastError());

return;

}

// Create worker threads

for( i=0; i < THREADCOUNT; i++ )

{

aThread[i] = CreateThread(

NULL, // default security attributes

0, // default stack size

(LPTHREAD_START_ROUTINE) WriteToDatabase,

NULL, // no thread function arguments

0, // default creation flags

&ThreadID); // receive thread identifier

if( aThread[i] == NULL )

{

printf("CreateThread error: %d/n", GetLastError());

return;

}

}

// Wait for all threads to terminate

WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);

// Close thread and mutex handles

for( i=0; i < THREADCOUNT; i++ )

CloseHandle(aThread[i]);

CloseHandle(ghMutex);

}

DWORD WINAPI WriteToDatabase( LPVOID lpParam )

{

DWORD dwCount=0, dwWaitResult;

// Request ownership of mutex.

while( dwCount < 20 )

{

dwWaitResult = WaitForSingleObject(

ghMutex, // handle to mutex

INFINITE); // no time-out interval

switch (dwWaitResult)

{

// The thread got ownership of the mutex

case WAIT_OBJECT_0:

__try {

// TODO: Write to the database

printf("Thread %d writing to database.../n",

GetCurrentThreadId());

dwCount++;

}

__finally {

// Release ownership of the mutex object

if (! ReleaseMutex(ghMutex))

{

// Handle error.

}

}

break;

// The thread got ownership of an abandoned mutex

// The database is in an indeterminate state

case WAIT_ABANDONED:

return FALSE;

}

}

return TRUE;

}

4)使用信号灯对象进行线程间同步:

信号灯对象是一个允许指定数量的线程获取的对象。信号灯对象一般是用于线程排队,考虑这样一种情况:服务器程序设置了3个工作线程以及n个和客户端连接的服务线程,服务线程需要在工作线程空闲时与之通讯,但3个工作线程都很忙的时候,就必须等待。这种情况下设置一个计数值为3的信号灯对象,服务线程和工作线程通讯前会首先尝试获取信号灯,这样就可以保证同时只有3个服务线程可以通过,而其他服务线程处于等待状态。

当信号灯对象的计数值为1时,就相当于一个互斥对象。

创建信号灯对象可以使用函数CreateSemaphore

HANDLE WINAPI CreateSemaphore(

__in_opt LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, //安全描述符

__in LONG lInitialCount, //初始时计数值

__in LONG lMaximumCount, //对象的最大计数值,即最多允许多少个线程获取该对象

__in_opt LPCTSTR lpName //指定对象的名字

);

释放信号灯对象使用函数ReleaseSemaphore

BOOL WINAPI ReleaseSemaphore(

__in HANDLE hSemaphore, //

__in LONG lReleaseCount, //指定释放时将计数值增加多少

__out_opt LPLONG lpPreviousCount //函数在此返回释放前的计数值,不需要的话可设为NULL

);

当不再需要信号灯对象时,可以调用CloseHandle函数将它关闭。

信号灯对象是靠WaitForSingleObject函数来获取的,当对象被某个线程获取后,它的计数值减1,当计数值未减到0时,对象的状态是置位的,这意味着还有线程可以继续获取该对象;当计数值减到0时,对象的状态被复位。

实例代码如下:

#include <windows.h>

#include <stdio.h>

#define MAX_SEM_COUNT 10

#define THREADCOUNT 12

HANDLE ghSemaphore;

DWORD WINAPI ThreadProc( LPVOID );

void main()

{

HANDLE aThread[THREADCOUNT];

DWORD ThreadID;

int i;

// Create a semaphore with initial and max counts of MAX_SEM_COUNT

ghSemaphore = CreateSemaphore(

NULL, // default security attributes

MAX_SEM_COUNT, // initial count

MAX_SEM_COUNT, // maximum count

NULL); // unnamed semaphore

if (ghSemaphore == NULL)

{

printf("CreateSemaphore error: %d/n", GetLastError());

return;

}

// Create worker threads

for( i=0; i < THREADCOUNT; i++ )

{

aThread[i] = CreateThread(

NULL, // default security attributes

0, // default stack size

(LPTHREAD_START_ROUTINE) ThreadProc,

NULL, // no thread function arguments

0, // default creation flags

&ThreadID); // receive thread identifier

if( aThread[i] == NULL )

{

printf("CreateThread error: %d/n", GetLastError());

return;

}

}

// Wait for all threads to terminate

WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);

// Close thread and semaphore handles

for( i=0; i < THREADCOUNT; i++ )

CloseHandle(aThread[i]);

CloseHandle(ghSemaphore);

}

DWORD WINAPI ThreadProc( LPVOID lpParam )

{

DWORD dwWaitResult;

BOOL bContinue=TRUE;

while(bContinue)

{

// Try to enter the semaphore gate.

dwWaitResult = WaitForSingleObject(

ghSemaphore, // handle to semaphore

0L); // zero-second time-out interval

switch (dwWaitResult)

{

// The semaphore object was signaled.

case WAIT_OBJECT_0:

// TODO: Perform task

printf("Thread %d: wait succeeded/n", GetCurrentThreadId());

bContinue=FALSE;

// Simulate thread spending time on task

Sleep(5);

// Release the semaphore when task is finished

if (!ReleaseSemaphore(

ghSemaphore, // handle to semaphore

1, // increase count by one

NULL) ) // not interested in previous count

{

printf("ReleaseSemaphore error: %d/n", GetLastError());

}

break;

// The semaphore was nonsignaled, so a time-out occurred.

case WAIT_TIMEOUT:

printf("Thread %d: wait timed out/n", GetCurrentThreadId());

break;

}

}

return TRUE;

}

你可能感兴趣的:(windows)