同步问题就是因为线程/进程 间异步执行访问共享数据而引起的数据冲突的问题(经典的例子:生产者与消费者问题),windows提供了一些同步对象来解决此类问题,包括:临界区对象、互斥体对象、事件对象、信号量对象,下面将对这些同步对象进行说明举例。
Win32 API提供了一组能使线程阻塞其自身执行的等待函数。这些函数在其参数中的一个或多个同步对象产生了信号,或者超过规定的等待时间才会返回。
在等待函数未返回时,线程处于等待状态,此时线程只消耗很少的CPU时间。使用等待函数既可以保证线程的同步,又可以提高程序的运行效率。
最常用的等待函数是:
DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);
//
监测单个的同步对象状态
DWORD WaitForMultipleObject(DWORD nCount,CONST HANDLE
*
lpHandles,BOOL bWaitAll,DWORD dwMilliseconds);
//
同时监测多个同步对象
这两个函数是线程同步关键的等待函数。
1)临界区对象
临界区对象可以保证同一时刻只有一个线程执行一段访问共享数据结构的代码区(临界区),当一个线程进入临界区,
只有等待该线程执行完临界区代码的指令后,其它线程才能访问临界区的代码,这样就能够有效地保护共享数据的完整性。
class
CShareData

...
{
private:
char m_buf[MAX];
CRITICAL_SECTION m_cs;
public:
CShareData()

...{
//初始化临界区
InitializeCriticalSection(&m_cs);
...
}

~CShareData()

...{
}

read()

...{
EnterCriticalSection(&m_cs);

....

LeaveCriticalSection(&m_cs);
}

write()

...{
EnterCriticalSection(&m_cs);

....

LeaveCriticalSection(&m_cs);
}
}
;

2)事件对象
事件对象有两种状态,无信号状态和有信号状态,在线程访问某一资源之前,需要
等待某一事件发生时,使用事件对象最为适合。
实例:
该实例保证先执行线程1(threadFun1),后执行线程2(threadFun2)
HANDLE hEvent1
=
NULL;
HANDLE hEvent2
=
NULL;

char
g_buf[
10
]
=
...
{0}
;

DWORD WINAPI threadFun1(LPVOID pParam)

...
{
WaitForSingleObject(hEvent1,INFINITE);
for(int i=0;i<10;i++)

...{
g_buf = 'a';
Sleep(200);
}
printf("thread1: %s ",g_buf);
SetEvent(hEvent2);
return 0;
}

DWORD WINAPI threadFun2(LPVOID pParam)

...
{
//当该函数返回后事件对象自动设置为无信号状态,这样其他线程也将被挂起
WaitForSingleObject(hEvent2,INFINITE);
for(int i=0;i<10;i++)

...{
g_buf = 'b';
Sleep(200);
}
printf("thread2: %s ",g_buf);
//SetEvent(hEvent);
return 0;
}

int
main(
int
argc,
char
*
argv[])

...
{
hEvent1 = CreateEvent(NULL, FALSE, FALSE, NULL);
SetEvent(hEvent1);

hEvent2 = CreateEvent(NULL, FALSE, FALSE, NULL);

HANDLE hThread1 = CreateThread(NULL,0,threadFun1,0,0,0);
HANDLE hThread2 = CreateThread(NULL,0,threadFun2,0,0,0);

Sleep(5000);
printf("%s ",g_buf);
return 0;
}

3)信号量对象
信号对象允许同时对多个线程共享资源进行访问,在创建对象时指定最大可同时访问的线程数。
当一个线程申请访问成功后,信号对象中的计数器减一,调用ReleaseSemaphore函数后,
信号对象中的计数器加一。其中,计数器值大于或等于0,但小于或等于创建时指定的最大值。
如果一个应用在创建一个信号对象时,将其计数器的初始值设为0,就阻塞了其他线程,保护了资源。
等初始化完成后,调用ReleaseSemaphore函数将其计数器增加至最大值,则可进行正常的存取访问。
经常使用的一个简单的应用例子是判断一个程序是否已经运行了,该内核对象可在不同进程间访问。
实例:
HANDLE hSemaphore;

DWORD WINAPI threadFun1(LPVOID pParam)

...
{
WaitForSingleObject(hSemaphore,INFINITE);
MessageBox(NULL,"thread1","hint",MB_OK);
ReleaseSemaphore(hSemaphore,1,NULL);
return 0;
}

DWORD WINAPI threadFun2(LPVOID pParam)

...
{
WaitForSingleObject(hSemaphore,INFINITE);
MessageBox(NULL,"thread2","hint",MB_OK);
ReleaseSemaphore(hSemaphore,1,NULL);
return 0;
}


DWORD WINAPI threadFun3(LPVOID pParam)

...
{
WaitForSingleObject(hSemaphore,INFINITE);
MessageBox(NULL,"thread3","hint",MB_OK);
ReleaseSemaphore(hSemaphore,1,NULL);
return 0;
}

int
main(
int
argc,
char
*
argv[])

...
{
hSemaphore = CreateSemaphore(NULL, 2, 2, NULL);
HANDLE hThread1 = CreateThread(NULL,0,threadFun1,0,0,0);
HANDLE hThread2 = CreateThread(NULL,0,threadFun2,0,0,0);
HANDLE hThread3 = CreateThread(NULL,0,threadFun3,0,0,0);

Sleep(100000);
return 0;
}

4)互斥体对象
互斥体对象与临界区对象相似,只是临界区对象只适用于同一个进程中。