windows下的三种同步方式:
1, 互斥对象
2, 事件对象
3, 临界区
互斥对象:属于内核对象, 它能够确保线程拥有对单个资源的互斥访问权.互斥对象包含一个使用数量, 一个线程ID和一个计数器.其中ID用于标识系统中的哪个线程当前拥有互斥对象, 计数器用于指明该线程拥有互斥对象的次数.
1, 声明一个句柄, 用于存放互斥对象
2, 创建一个互斥对象
3, 在线程中得到该对象的拥有权
4, 释放拥有权.
关于互斥对象, 谁拥有谁释放.
#include <Windows.h>
#include <iostream>
int Afxcounts = 1000;
unsigned long _stdcall MutiFun1(void * lpParam);
unsigned long _stdcall MutiFun2(void * lpParam);
HANDLE hlocker; //互斥对象的句柄
int main()
{
//CreateMutex 创建互斥对象, 返回该对象的句柄,
//如果指定名称的互斥对象以存在则返回已存在互斥对象的句柄
hlocker = CreateMutex(NULL, //使用NULL, 说明使用默认的安全性
FALSE, //指定互斥对象初始的拥有者, TRUE, 则创建这个互斥对象的线程获得该对象的所有权
"locker" /*互斥对象的名称, NULL, 说明为一个匿名互斥对象*/);
HANDLE h1 = CreateThread(NULL, 0, MutiFun1, NULL, 0, NULL);
HANDLE h2 = CreateThread(NULL, 0, MutiFun2, NULL, 0, NULL);
CloseHandle(h1);
CloseHandle(h2);
std::cin.get();
return 0;
}
unsigned long _stdcall MutiFun1(void * lpParam)
{
while (Afxcounts > 0)
{
//WaitForSingleObject 主动请求互斥对象的所有权.
WaitForSingleObject(hlocker, //互斥对象句柄
INFINITE/*时间间隔, 以毫秒为单位, INFINITE 一直等待到对象处于有信号状态才会返回*/);
std::cout<<"Thread 1 : left counts : "<<Afxcounts--<<std::endl;
//ReleaseMutex 释放指定对象的所有权, 参数为互斥对象的句柄
ReleaseMutex(hlocker);
}
return 0;
}
unsigned long _stdcall MutiFun2(void * lpParam)
{
while (Afxcounts > 0)
{
WaitForSingleObject(hlocker, INFINITE);
std::cout<<"Thread 1 : left counts : "<<Afxcounts--<<std::endl;
ReleaseMutex(hlocker);
}
return 0;
}
在调用CreateMutex函数创建一个命名的互斥对象后, 如果其返回值是一个有效的句柄, 那么可以接着调用GetLastError函数, 如果该函数返回的是ERROR_ALREADYEXISTS,就表明先前已经创建了这个命名的互斥对象.
由于互斥对象属于WINDOWS内核对象, 所以我们可以通过它来验证程序是否已经开始运行:
#include <Windows.h>
#include <iostream>
HANDLE mutexLocker;
int main()
{
mutexLocker = CreateMutex(NULL, TRUE, "LOCKER");
if (mutexLocker)
{
if (ERROR_ALREADY_EXISTS == GetLastError())
{
std::cout<<"the app is running.\n";
return -1;
}
}
std::cin.get();
return 0;
}
使用互斥对象要注意: 如果多次在同一个线程中请求同一个互斥对象, 那么就需要相应地多次调用ReleaseMutex函数释放该互拆对象.
例: 当主线程拥有互斥对明时, 该对象就处于未通知状态, 但是当在主线程中调用WaitForSingleObject函数请求该互斥对象的所有权时, 因为请求的线程的ID和该互斥对象当产所有者的线程的ID是相同的, 所以仍然能够请求到这个互斥对象, 操作系统通过互斥对象内部的计数器来维护同一个线程请求到该互斥对象的次数, 于是该计数器就会加1, 这时, 互斥对象内部计数器的值为2, 当接下来调用ReleaseMutex函数释放该互斥对象的所有权时, 实际上就是递减这个计数器, 但此时该计数器的值为1, 因此操作系统不会将这个互斥对象变为已通知状态.
事件对象: 也属于内核对象, 它包以下三个成员:
1, 使用计数
2, 用于指明该事件是一个自动重置的事件还是一个人工重置的事件的布尔值.
3, 用于指明该事件处于已通知状态还是未能通知状态的布尔值.
事件对象有两种不同的类型: 人工重置的事件对象和自动重置的事件对象.
但是, 如果为了实现线程间的同步, 不应该使用人工重置的事件对, 而应该使用自动重置的事件对象.
#include <Windows.h>
#include <iostream>
HANDLE eventLocker;
int Afxcounts = 1000;
unsigned long _stdcall MutiFun1(void * lpam);
unsigned long _stdcall MutiFun2(void * lpam);
int main()
{
eventLocker = CreateEvent(NULL, //NULL, 默认安全级别
FALSE, //TRUE : 人工重置对象, FALSE : 自动重置对象
FALSE, //TRUR : 对象初始为有信号, FALSE : 为无信号
"locker"/*对象名称*/);
SetEvent(eventLocker); //设置该事件对象为有信号
HANDLE h1 = CreateThread(NULL, 0, MutiFun1, NULL, 0, NULL);
CloseHandle(h1);
HANDLE h2 = CreateThread(NULL, 0, MutiFun2, NULL, 0, NULL);
CloseHandle(h2);
std::cin.get();
return 0;
}
unsigned long _stdcall MutiFun1(void * lpam)
{
while (Afxcounts >0)
{
WaitForSingleObject(eventLocker, INFINITE);
std::cout<<"Thread 1 : "<<Afxcounts --<<std::endl;
SetEvent(eventLocker); //将事件对象设置为有信号
}
return 0;
}
unsigned long _stdcall MutiFun2(void * lpam)
{
while (Afxcounts >0)
{
WaitForSingleObject(eventLocker, INFINITE);
std::cout<<"Thread 2 : "<<Afxcounts --<<std::endl;
SetEvent(eventLocker);
}
return 0;
}
PS:
当人工重置的事件对象得到通知时, 等待该事件对象的所有线程均变为可调度线程;当一个自动重置的事件对象得到通知时, 等待该事件对象的线程中只有一个线程变为可调度线程, 同时操作系统会将该事件对象设置为无信号状态, 这样, 当对所保护的代码执行完成后, 需要调用SetEvent将该事件对象设置为有信号状态.而人工重置的事件对象, 在一个线程得到该事件对象之后, 操作系统并不会将该事件对象设置为无信息状态, 除非显式调用ResetEvent将其设置为无信号状态, 否则该对象会一直是有信号状态.
本文介绍了Windows下线程同步的三种方式,重点讲解了互斥对象和事件对象。互斥对象确保资源的互斥访问,通过CreateMutex创建并使用GetLastError检查是否存在。事件对象分为自动重置和人工重置,自动重置适合线程同步,避免多个线程同时调度。
1087

被折叠的 条评论
为什么被折叠?



