`

《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核心编程系列》谈谈用户模式下的线程同步

    系统中的线程必须访问系统资源,如堆、...  线程同步包括许多方面,windows提供了许多基础设施使线程同步变得容易。  用户模式下的线程同步:方法一,原子访问  线程同步的一大部分与原子访问有关。所谓原子访

    Windows核心编程培训 .pptx

    Windows核心编程培训相关知识,包括内核对象、进程、线程及线程同步、内存映射文件 、动态链接库等知识点。

    Windows系统编程 原书第4版pdf

    该书主要是利用windows API进行应用程序开发,有文件系统、进程和线程管理、进程间通信、网络编程以及同步等核心系统服务。

    Windows应用程序捆绑核心编程光盘代码

    3.2.1 Windows进程间标准通信技术的发展 55 3.2.2 应用程序与进程 56 3.2.3 进程之间通信的类型 56 3.3 使用自定义消息通信 57 3.3.1 通过自定义消息实现进程间通信的方法 57 3.3.2 通过自定义消息实现进程间...

    条件变量和读写锁实现线程同步

    //windows核心编程5th 生产者部分 请求独占锁锁 AcquireSRWLockExclusive(&g_srwLock) 程序处于运行状态,并且数据已满:生产者转入休眠状态 SleepConditionVariableSRW(&g_cvReadyToProduce, &g_srwLock, INFINITE,...

    精通WindowsAPI 函数 接口 编程实例

    2.3 Windows API核心DLL 21 2.3.1 Kernel32.dll 21 2.3.2 User32.dll 21 2.3.3 Gdi32.dll 22 2.3.4 标准C函数 22 2.3.5 其他Dll 22 2.4 Unicode和多字节 22 2.4.1 W版本和A版本的API 24 2.4.2 ...

    精通Windows.API-函数、接口、编程实例.pdf

    2.3 Windows API核心DLL 21 2.3.1 Kernel32.dll 21 2.3.2 User32.dll 21 2.3.3 Gdi32.dll 22 2.3.4 标准C函数 22 2.3.5 其他Dll 22 2.4 Unicode和多字节 22 2.4.1 W版本和A版本的API 24 2.4.2 ...

    Winsock 完成端口模型封装的全新类

    如果不了解,推荐几本书:《Inside Windows 2000,《WINDOWS核心编程》,《WIN32多线程程序设计》、《WINDOWS网络编程技术》。在去年,我在C语言下用完成端口模型写了一个WEBSERVER,前些天,我决定用C++重写这个WEB...

    Windows 程序设计(第5版)(上、下册)--详细书签版

    在《Window程序设计》(第5版)中,这位杰出的“Windows先锋奖”(Windows Pioneer Award)获得者根据最新的Windows操作系统权威技术修订了他的经典著作——再一次演示了基本的Win32程序设计的API核心内容。...

    游戏编程--大师技巧

    第一部分 Windows编程基础  第一章 无尽之旅  历史一瞥  设计游戏  游戏类型  集思广益  设计文档和情节图板  使游戏具有趣味性  游戏的构成  常规游戏编程指导  使用工具  从准备到完成一使用编译器  ...

    windows驱动开发技术详解-part2

    Windows操作系统的基本原理、NT驱动程序与WDM驱动程序的构造、驱动程序中的同步异步处理方法、驱 动程序中即插即用功能、驱动程序的各种调试技巧等。同时,还针对流行的PCI驱动程序、USB驱动程序 、虚拟串口驱动...

Global site tag (gtag.js) - Google Analytics