1、为什么线程要同步?
#include<windows.h>
#include<iostream>
using namespace std;
DWORD WINAPI ThreadFunc1(PVOID pvParam)
{
cout << "线程1:创建成功!" << endl;
return 0;
}
DWORD WINAPI ThreadFunc2(PVOID pvParam)
{
cout << "线程2:创建成功!" << endl;
return 0;
}
DWORD WINAPI ThreadFunc3(PVOID pvParam)
{
cout << "线程3:创建成功!" << endl;
return 0;
}
int main(int argc, char* argv[])
{
HANDLE ThreadHandle1, ThreadHandle2, ThreadHandle3;
ThreadHandle1 = CreateThread(NULL, 0, ThreadFunc1, NULL, 0, NULL);
ThreadHandle2 = CreateThread(NULL, 0, ThreadFunc2, NULL, 0, NULL);
ThreadHandle3 = CreateThread(NULL, 0, ThreadFunc3, NULL, 0, NULL);
getchar();
return 0;
}
举一个例子,创建三个线程。根据结果得知:操作系统随机调用线程,不能预知线程的执行顺序。
2、Windows线程同步方法主要有临界区、事件、互斥量、信号量四种。
3、临界区
临界区—相关API函数
定义临界区对象(通常全局变量)
CRITICAL_SECTION cs
临界区对象初始化
InitializeCriticalSection(&cs)
进入临界区
EnterCriticalSection(&cs)
离开临界区
LeaveCriticalSection(&cs)
释放临界区对象
DeleteCriticalSection(&cs)
利用临界区相关API函数,上述程序可以修改为:
#include<windows.h>
#include<iostream>
using namespace std;
CRITICAL_SECTION cs;
DWORD WINAPI ThreadFunc1(PVOID pvParam)
{
EnterCriticalSection(&cs);
cout << "线程1:创建成功!" << endl;
LeaveCriticalSection(&cs);
return 0;
}
DWORD WINAPI ThreadFunc2(PVOID pvParam)
{
EnterCriticalSection(&cs);
cout << "线程2:创建成功!" << endl;
LeaveCriticalSection(&cs);
return 0;
}
DWORD WINAPI ThreadFunc3(PVOID pvParam)
{
EnterCriticalSection(&cs);
cout << "线程3:创建成功!" << endl;
LeaveCriticalSection(&cs);
return 0;
}
int main(int argc, char* argv[])
{
InitializeCriticalSection(&cs);
HANDLE ThreadHandle1, ThreadHandle2, ThreadHandle3;
ThreadHandle1 = CreateThread(NULL, 0, ThreadFunc1, NULL, 0, NULL);
ThreadHandle2 = CreateThread(NULL, 0, ThreadFunc2, NULL, 0, NULL);
ThreadHandle3 = CreateThread(NULL, 0, ThreadFunc3, NULL, 0, NULL);
getchar();
DeleteCriticalSection(&cs);
return 0;
}
虽然临界区同步速度很快,但是只能用来同步本进程内的线程,不能同步多个进程中的线程。
4、两个重要的同步等待函数
DWORD WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds 0:立即返回;INFINITE:永远等待
);
返回值:
WAIT_OBJECT_0 线程等待的对象变为已通知状态
WAIT_TIMEOUT 超时
WAIT_FAILED 将无效的句柄传递给WaitForSingleObject
DWORD WaitForMultipleObject(
DWORD dwCount, 指明内核对象的数量
CONST HANDLE* phHandle, 指向内核对象句柄的数组指针
BOOL fWaitAll, true:所有对象变为已通知前,不允许调用线程
DWORD dwMilliseconds
);
上述的程序代码又可以改写为:
#include<windows.h>
#include<iostream>
using namespace std;
HANDLE ThreadHandle1, ThreadHandle2, ThreadHandle3;
DWORD WINAPI ThreadFunc1(PVOID pvParam)
{
cout << "线程1:创建成功!" << endl;
return 0;
}
DWORD WINAPI ThreadFunc2(PVOID pvParam)
{
WaitForSingleObject(ThreadHandle1, INFINITE);
cout << "线程2:创建成功!" << endl;
return 0;
}
DWORD WINAPI ThreadFunc3(PVOID pvParam)
{
WaitForSingleObject(ThreadHandle2, INFINITE);
cout << "线程3:创建成功!" << endl;
return 0;
}
int main(int argc, char* argv[])
{
ThreadHandle1 = CreateThread(NULL, 0, ThreadFunc1, NULL, 0, NULL);
ThreadHandle2 = CreateThread(NULL, 0, ThreadFunc2, NULL, 0, NULL);
ThreadHandle3 = CreateThread(NULL, 0, ThreadFunc3, NULL, 0, NULL);
getchar();
return 0;
}
线程1直接创建,线程2等待线程1创建,线程3等待线程2创建。
5、互斥量
互斥量—API函数
互斥量的创建,返回句柄
HANDLE CreateMutex(
PSECURITY_ATTRIBUTES psa, 安全属性的指针
BOOL bInitialOwner, 初始化互斥对象的所有者
PCTSTR pszName 指向互斥对象名的指针
);
为现有的一个已命名互斥对象创建一个新句柄
HANDLE openMutex(
DOWRD fdwAccess,
BOOL bInheritHandle,
PCTSTR pszName
);
释放互斥量
HANDLE ReleaseMutex(HANDLE hMutex);
等待互斥量
DOWRD WaitForSingleObject(
HANDLE hHandle,
DOWRD dwMilliseconds
);
上述程序又可以改写为:
#include<windows.h>
#include<iostream>
using namespace std;
HANDLE ThreadHandle1, ThreadHandle2, ThreadHandle3;
HANDLE ThreadMutex;
DWORD WINAPI ThreadFunc1(PVOID pvParam)
{
WaitForSingleObject(ThreadMutex, INFINITE);
cout << "线程1:创建成功!" << endl;
ReleaseMutex(ThreadMutex);
return 0;
}
DWORD WINAPI ThreadFunc2(PVOID pvParam)
{
WaitForSingleObject(ThreadMutex, INFINITE);
cout << "线程2:创建成功!" << endl;
ReleaseMutex(ThreadMutex);
return 0;
}
DWORD WINAPI ThreadFunc3(PVOID pvParam)
{
WaitForSingleObject(ThreadMutex, INFINITE);
cout << "线程3:创建成功!" << endl;
ReleaseMutex(ThreadMutex);
return 0;
}
int main(int argc, char* argv[])
{
ThreadMutex = CreateMutex(NULL, false, "tMutex");
ThreadHandle1 = CreateThread(NULL, 0, ThreadFunc1, NULL, 0, NULL);
ThreadHandle2 = CreateThread(NULL, 0, ThreadFunc2, NULL, 0, NULL);
ThreadHandle3 = CreateThread(NULL, 0, ThreadFunc3, NULL, 0, NULL);
getchar();
return 0;
}
注意CreateMutex
第二个参数为false
,说明当前主线程并不拥有互斥对象。
6、事件
事件—API函数
创建事件内核对象,返回句柄
HANDLE CreateEvent(
PSECURITY_ATTRIBUTES psa, 安全属性
BOOL fManuaResrt, 复位方式,false自动
BOOL fInitialState, 初始状态,true有信号
PCTSTR pszName 对象名称
);
打开一个已经存在的命名事件对象
HANDLE OpenEvent(
DOWRD fdwAccess,
BOOL fInherit,
PCTSTR pszName
);
EVENT_ALL_ACCESS 要求对事件对象进行完全访问
EVENT_MODIFY_STATE 允许SetEvent和ResetEvent函数
SYNCHRONIZE 允许事件对象的使用同步
将事件设置为已通知状态
BOOL SetEvent(HANDLE hEvent);
将事件设置为未通知状态
BOOL ResetEvent(HANDLE hEvent);
上述程序可以改为:
#include<windows.h>
#include<iostream>
using namespace std;
HANDLE ThreadHandle1, ThreadHandle2, ThreadHandle3;
HANDLE ThreadEvent;
DWORD WINAPI ThreadFunc1(PVOID pvParam)
{
WaitForSingleObject(ThreadEvent, INFINITE);
cout << "线程1:创建成功!" << endl;
SetEvent(ThreadEvent);
return 0;
}
DWORD WINAPI ThreadFunc2(PVOID pvParam)
{
WaitForSingleObject(ThreadEvent, INFINITE);
cout << "线程2:创建成功!" << endl;
SetEvent(ThreadEvent);
return 0;
}
DWORD WINAPI ThreadFunc3(PVOID pvParam)
{
WaitForSingleObject(ThreadEvent, INFINITE);
cout << "线程3:创建成功!" << endl;
SetEvent(ThreadEvent);
return 0;
}
int main(int argc, char* argv[])
{
ThreadEvent = CreateEvent(NULL, false, true, "tEvent");
ThreadHandle1 = CreateThread(NULL, 0, ThreadFunc1, NULL, 0, NULL);
ThreadHandle2 = CreateThread(NULL, 0, ThreadFunc2, NULL, 0, NULL);
ThreadHandle3 = CreateThread(NULL, 0, ThreadFunc3, NULL, 0, NULL);
getchar();
return 0;
}
注意CreateEvent
中的第三个参数要设置为true
,一开始就是有信号的。
其中,每个线程函数还可以改写为:
DWORD WINAPI ThreadFunc1(PVOID pvParam)
{
HANDLE event1=OpenEvent(EVENT_ALL_ACCESS,true,"tEvent");
WaitForSingleObject(event1,INFINITE);
cout<<"线程1:创建成功!"<<endl;
SetEvent(ThreadEvent);
return 0;
}
7、信号量
信号量—API函数
创建信号量
HANDLE CreateSemaphore(
PSECURITY_ATTRIBUTES psa,
LONG lInitialCount, 当前可用资源数
LONG lMaximumCount, 允许最大资源数
PCTSTR pszName
);
为现有的一个已命名信号机对象创建一个新句柄
HANDLE OpenSemaphore(
DWORD fdwAccess,
BOOL bInheritHandle,
PCTSTR pszName
);
SEMAPHORE_ALL_ACCESS 要求对信号量的完全访问
SEMAPHORE_MODIFY_STATE 允许使用ReleaseSemaphore函数
SYNCHRONIZE 允许使用信号量同步
释放信号量
ReleaseSemaphore(
HANDLE hSem,
LONG lReleaseCount,
PLONG plPreviousCount
);
上述程序可以改写为:
#include<windows.h>
#include<iostream>
using namespace std;
HANDLE ThreadHandle1, ThreadHandle2, ThreadHandle3;
HANDLE ThreadSemaphore;
LONG a = 0;
DWORD WINAPI ThreadFunc1(PVOID pvParam)
{
WaitForSingleObject(ThreadSemaphore, INFINITE);
cout << "线程1:创建成功!" << endl;
ReleaseSemaphore(ThreadSemaphore, 1, &a);
return 0;
}
DWORD WINAPI ThreadFunc2(PVOID pvParam)
{
WaitForSingleObject(ThreadSemaphore, INFINITE);
cout << "线程2:创建成功!" << endl;
ReleaseSemaphore(ThreadSemaphore, 1, &a);
return 0;
}
DWORD WINAPI ThreadFunc3(PVOID pvParam)
{
WaitForSingleObject(ThreadSemaphore, INFINITE);
cout << "线程3:创建成功!" << endl;
ReleaseSemaphore(ThreadSemaphore, 1, &a);
return 0;
}
int main(int argc, char* argv[])
{
ThreadSemaphore = CreateSemaphore(NULL, 1, 3, NULL);
ThreadHandle1 = CreateThread(NULL, 0, ThreadFunc1, NULL, 0, NULL);
ThreadHandle2 = CreateThread(NULL, 0, ThreadFunc2, NULL, 0, NULL);
ThreadHandle3 = CreateThread(NULL, 0, ThreadFunc3, NULL, 0, NULL);
getchar();
return 0;
}
注意CreateSemaphore
中的参数设置,为了防止乱序产生,当前可用的资源数应该设置为1。
8、总结
互斥量、事件、信号量都是内核对象,可用于进程间的线程同步;临界区只能用于进程内的线程同步。虽然在同一个进程内同步时,互斥量和临界区的功能相似,但是临界区的性能更好。
事件和其他几个同步方法的不同在于事件的主要作用不是保护共享资源,而是用于等待某个事件和在特定的事件发生时的发送信号,以协调线程之间的动作。
信号量与其他同步方法的区别在于它允许一个以上的线程同时访问共享资源,而其他线程同步方法都保证同时只能有一个线程访问共享资源。信号量的主要功能是用于资源计数。