Windows系统提供HANDLE WINAPI CreateSemaphore(xxx)
和HANDLE WINAPI CreateMutex(xxx)
直接提供Mutex和Semaphore两种内核对象供程序员使用在临界区互斥操作。在前面的多线程基础之一提到过Semaphore存在Binary和Counting两种,其中Binary Semaphore等同于Mutex,而Counting Semaphores则存在资源计数的选项,并且还存在可以为Counting Semaphores配备等待队列实现非阻塞请求以实现加速操作。
但是我们仔细查看创建Semaphore的Windows API:
HANDLE WINAPI CreateSemaphore(
_In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes
_In_ LONG lInitialCount,
_In_ LONG lMaximumCount,
_In_opt_ LPCTSTR lpName
);
第一个参数:安全属性,如果为NULL则是默认安全属性
第二个参数:信号量的初始值,要>=0且<=第三个参数
第三个参数:信号量的最大值,即最大资源数目
第四个参数:信号量的名称,一般不涉及到跨进程使用基本都是输入NULL的。
可以发现并不存在开关配备等待队列的选项,是否这意味着Windows系统提供的Semaphore并没有提供这一选项还是内嵌了等待队列?这需要代码检验一下。
test_time_Mutex.cpp
#include <iostream>
#include <Windows.h>
#include <time.h>
using namespace std;
HANDLE g_hMutex = NULL;
const int g_Number = 50;
const int killTimeStep = 100000000;
DWORD WINAPI ThreadProc(__in LPVOID lpParameter);
int main()
{
clock_t start_time = clock();
//TRUE代表主线程拥有互斥对象 但是主线程没有释放该对象 互斥对象谁拥有 谁释放
g_hMutex = CreateMutex(NULL,TRUE,NULL);
printf("主线程创建时便占有了互斥对象,没有释放,所以其他子线程无法使用。\n");
HANDLE hThread[ g_Number ] = {0}; //创建50个线程
int first = 1;
for (int i=0; i<g_Number; i++)
hThread[i] = CreateThread(NULL, 0, ThreadProc, (LPVOID)first++, 0, NULL);
printf("创建大规模子线程成功\n");
ReleaseMutex(g_hMutex);
printf("主线程释放了互斥对象,其他子线程可以开始使用。\n");
WaitForMultipleObjects(g_Number,hThread,TRUE,INFINITE);
for (int i=0; i<g_Number; i++)
CloseHandle( hThread[i] );
CloseHandle( g_hMutex );
clock_t end_time = clock();
cout<<"Running time is:"<<static_cast<double>(end_time - start_time)/CLOCKS_PER_SEC*1000<<"ms"<<endl;
return 0;
}
DWORD WINAPI ThreadProc(__in LPVOID lpParameter)
{
WaitForSingleObject(g_hMutex, INFINITE);//等待互斥量
cout<<(int)lpParameter<<endl;
int i = killTimeStep;
while(i--);
ReleaseMutex(g_hMutex);//释放互斥量
return 0;
}
运行结果
********************************……********************************
test_time_Semaphores.cpp
#include <iostream>
#include <Windows.h>
#include <time.h>
using namespace std;
HANDLE g_Sema = NULL;
const int g_Number = 50;
const int killTimeStep = 100000000;
DWORD WINAPI ThreadProc(__in LPVOID lpParameter);
int main()
{
clock_t start_time = clock();
g_Sema = CreateSemaphore(NULL,0,3,NULL);
printf("主线程创建时便占有了互斥对象,没有释放,所以其他子线程无法使用。\n");
ReleaseSemaphore(g_Sema,1,NULL);
printf("主线程释放了互斥对象,其他子线程可以开始使用。\n");
HANDLE hThread[ g_Number ] = {0}; //创建50个线程
int first = 1;
for (int i=0; i<g_Number; i++)
hThread[i] = CreateThread(NULL, 0, ThreadProc, (LPVOID)first++, 0, NULL);
WaitForMultipleObjects(g_Number,hThread,TRUE,INFINITE);
for (int i=0; i<g_Number; i++)
CloseHandle( hThread[i] );
CloseHandle( g_Sema );
clock_t end_time = clock();
cout<<"Running time is:"<<static_cast<double>(end_time - start_time)/CLOCKS_PER_SEC*1000<<"ms"<<endl;
return 0;
}
DWORD WINAPI ThreadProc(__in LPVOID lpParameter)
{
WaitForSingleObject(g_Sema, INFINITE);//等待信号量
cout<<(int)lpParameter<<endl;
int i = killTimeStep;
while(i--);
ReleaseSemaphore(g_Sema,1,NULL);//释放信号量
return 0;
}
运行结果
********************************……********************************
Conclusion: 可以看到Windows确实没有为Semaphore配备等待队列,所以导致在测试代码中Semaphore和Mutex在同样的条件下都是采用“阻塞”请求机制,即在线程获取CPU的轮转时间片时,会一直调用WaitForSingleObject(xxx)询问情况,直到耗光本轮时间片或得到互斥对象。
对于操作损耗较短的临界区,性能影响并不明显(进程上下文切换的花费甚至可能还抵不过进程非阻塞请求节省下来的时间)。但是如果临界区的操作消耗时间较长,显然为Semaphore配备等待队列机制是存在需求的。参见多线程基础之六:采用Pthread Win32提供的配备等待队列的Semaphore填补Windows未提供非阻塞请求的Semaphore机制。