四种进程或线程同步互斥的控制方法
1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
2、互斥量:为协调共同对一个共享资源的单独访问而设计的。
3、信号量:为控制一个具有有限数量用户资源而设计。
4、事件:用来通知线程有一些事件已发生,从而启动后继任务的开始。
临界区例子代码:
#include <windows.h>
#include <stdio.h>
#include <iostream>
using namespace std;
DWORD WINAPI funone(LPVOID lpParameter);
DWORD WINAPI funtwo(LPVOID lpParameter);
static int a = 0;
CRITICAL_SECTION Section;
int main()
{
HANDLE handleone, handletwo;
handleone = CreateThread(NULL, 0, funone, NULL, 0, NULL);
handletwo = CreateThread(NULL, 0, funtwo, NULL, 0, NULL);
::CloseHandle(handleone);
::CloseHandle(handletwo);
InitializeCriticalSection(&Section);
Sleep(1000);
cout << "Please enter 'q'" << endl;
if (getchar() == 'q')
{
DeleteCriticalSection(&Section);
}
else
{
return 0;
}
}
DWORD WINAPI funtwo(LPVOID lpParamenter)
{
while (1)
{
EnterCriticalSection(&Section);
a++;
Sleep(1000);
cout << "ThreadTwo is running:" << a << endl;
LeaveCriticalSection(&Section);
}
return 0;
}
DWORD WINAPI funone(LPVOID lpParamenter)
{
while (1)
{
EnterCriticalSection(&Section);
a++;
Sleep(1000);
cout << "ThreadOne is running:" << a << endl;
LeaveCriticalSection(&Section);
}
return 0;
}
互斥对象例子代码:
#include <iostream>
#include <windows.h>
using namespace std;
HANDLE hMutex;
DWORD WINAPI Fun(LPVOID lpParamter)
{
while (1)
{
WaitForSingleObject(hMutex, INFINITE);
cout << "Fun display!" << endl;
Sleep(1000);
ReleaseMutex(hMutex);
}
}
int main()
{
HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL);
hMutex = CreateMutex(NULL, FALSE, "screen");
CloseHandle(hThread);
while (1)
{
/*等待函数可使线程自愿进入等待状态,直到一个特定的内核对象变为已通知状态为止。*/
WaitForSingleObject(hMutex, INFINITE); //等待线程释放,等待时间为无限时间INFINITE
cout << "main display!" << endl;
Sleep(2000);
/*
如果一个线程拥有了一个互斥对象后,当该线程运行完成后就要释放该互斥对象,不然其他的线程得不到互斥对象则无法运行。
用ReleaseMutex(HWND);操作
*/
ReleaseMutex(hMutex); //释放由线程拥有的一个互斥体的控制权
}
return 0;
}
/*
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, 安全属性结构指针
BOOL bInitialOwner, //是否占有该互斥量
LPCTSTR lpName //设置互斥对象的名字
);*/
事件对象例子代码:
#include <Windows.h>
#include <iostream>
using namespace std;
//声明线程函数
DWORD WINAPI funone(LPVOID lpParameter);
DWORD WINAPI funtwo(LPVOID lpParameter);
HANDLE hevent;
static int a = 0;
int main()
{
// 创建两个线程句柄
HANDLE handleone, handletwo;
handleone = CreateThread(NULL, 0, funone, NULL, 0, NULL);
cout << "线程1开始运行!" << endl;
handletwo = CreateThread(NULL, 0, funtwo, NULL, 0, NULL);
cout << "线程2开始运行!" << endl;
// 创建时间对象,当信号为1的时候表示线程可用。
hevent = CreateEvent(NULL, FALSE, true, NULL);
CloseHandle(handleone);
CloseHandle(handletwo);
//主线程睡10秒,让别的线程函数有机会获得CPU时间片
Sleep(10000);
return 0;
}
DWORD WINAPI funone(LPVOID lpParameter)
{
while (1)
{
WaitForSingleObject(hevent, INFINITE);
ResetEvent(hevent);
if (a < 10000)
{
a+=1;
cout << "线程1:" << a << endl;
SetEvent(hevent);
}
else
{
SetEvent(hevent);
break;
}
}
return 0;
}
DWORD WINAPI funtwo(LPVOID lpParameter)
{
while (1)
{
WaitForSingleObject(hevent, INFINITE);
ResetEvent(hevent);
if (a < 10000)
{
a+=1;
cout << "线程2:" << a << endl;
SetEvent(hevent);
}
else
{
SetEvent(hevent);
break;
}
}
return 0;
}
/*
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,// 安全属性
BOOL bManualReset,// 复位方式
BOOL bInitialState,// 初始状态
LPCTSTR lpName // 对象名称
);
lpEventAttributes[输入] 一个指向SECURITY_ATTRIBUTES结构的指针,确定返回的句柄是否可被子进程继承。如果lpEventAttributes是NULL,此句柄不能被继承。
bManualReset[输入] 指定将事件对象创建成手动复原还是自动复原。如果是TRUE,那么必须用ResetEvent函数来手工将事件的状态复原到无信号状态。如果设置为FALSE,当一个等待线程被释放以后,系统将会自动将事件状态复原为无信号状态。
bInitialState[输入] 指定事件对象的初始状态。如果为TRUE,初始状态为有信号状态;否则为无信号状态。
lpName[输入] 指定事件的对象的名称,是一个以0结束的字符串指针。名称的字符格式限定在MAX_PATH之内。名字是对大小写敏感的。
*/
信号量例子代码:
#include <windows.h>
#include <iostream>
using namespace std;
DWORD WINAPI ThreadProc(LPVOID);
HANDLE ghSemaphore;
int main()
{
HANDLE aThread[12];
DWORD ThreadID;
int i;
/*创建一个新的信号量:零值表示采用不允许继承的默认描述符,设置信号量的初始计数,设置信号量的最大计数,指定信号量对象的名称*/
ghSemaphore = CreateSemaphore(NULL, 16, 16, NULL);
if (ghSemaphore == NULL)
{
cout << "Create Semaphore error:" << GetLastError()<<endl;
return 0;
}
/*初始化线程*/
for (i = 0; i < 12; i++)
{
aThread[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL,0,&ThreadID);
if (aThread[i] == NULL)
{
cout << "Create Thread Error:" << GetLastError() << endl;
return 0;
}
}
/*等待进程结束:
句柄的数量,
句柄数组的指针,
等待的类型(如果为TRUE 则等待所有信号量有效在往下执行,FALSE 当有其中一个信号量有效时就向下执行),
超时时间 超时后向执行(如果为INFINITE 永不超时。如果没有信号量就会在这死等)
*/
WaitForMultipleObjects(12, aThread, TRUE, INFINITE);
for (i = 0; i < 12; i++)
{
CloseHandle(aThread[i]);
}
CloseHandle(ghSemaphore);
system("pause");
return 0;
}
DWORD WINAPI ThreadProc(LPVOID lpPram)
{
DWORD dwWaitResult;
BOOL bContinue = TRUE;
while (bContinue)
{
/*
WaitForSingleObject函数用来检测hHandle事件的信号状态,
在某一线程中调用该函数时,线程暂时挂起,
如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;
如果超时时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回。
参数dwMilliseconds有两个具有特殊意义的值:0和INFINITE。
若为0,则该函数立即返回;若为INFINITE,则线程一直被挂起,直到hHandle所指向的对象变为有信号状态时为止。
*/
dwWaitResult = WaitForSingleObject(ghSemaphore, 3L);
switch (dwWaitResult)
{
/*WAIT_OBJECT_0:指定的对象出有有信号状态*/
case WAIT_OBJECT_0:
cout << "Thread " << GetCurrentThreadId() << ":wait successed!"<<endl;
bContinue = FALSE;
Sleep(500);
for (int x = 0; x < 12; x++)
{
cout << "Thread " << GetCurrentThreadId() << " is runing!" << endl;
}
/*ReleaseSemaphore函数用于对指定的信号量增加指定的值*/
if (!ReleaseSemaphore(ghSemaphore, 1, NULL))
{
cout << "ReleaseSemaphore error:" << GetLastError() << endl;
}
break;
/*等待超时*/
case WAIT_TIMEOUT:
cout << "Thread " << GetCurrentThreadId() << " worked time out!" << endl;
break;
}
}
return TRUE;
}
总结:
1.互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。
2.互斥量(Mutex),信号灯(Semaphore),事件(Event)都可以被跨越进程使用来进行同步数据操作,而其他的对象与数据同步操作无关,但对于进程和线程来讲,如果进程和线程在运行状态则为无信号状态,在退出后为有信号状态。所以可以使用WaitForSingleObject来等待进程和?线程退出。
3.通过互斥量可以指定资源被独占的方式使用,但如果有下面一种情况通过互斥量就无法处理,比如现在一位用户购买了一份三个并发访问许可的数据库系统,可以根据用户购买的访问许可数量来决定有多少个线程/进程能同时进行数据库操作,这时候如果利用互斥量就没有办法完成这个要求,信号灯对象可以说是一种资源计数器。