线程同步,如果光从字面上看,这四个字并不好理解。什么叫线程的同步?让线程同时都在运行,显然不是如此。多线程的出现,就是为了让每个子线程做独立的事情,而这里面经常发生的一个问题是,子线程做独立的事情时却要使用同一个资源(即共享资源,常常是全局变量)。所以,我更喜欢称之为线程的协调,使线程协调访问共享资源,而不是在同一时刻访问它。
举个例子,我们平时的火车售票系统,其中定义了一个变量tickets,是全局变量,也是各个子线程都可以访问的共享资源。这个时候,如果有两个子线程同时访问这个变量,势必会出现售票混乱的问题。当然,文字性的叙述永远都不够清晰明了。
实现线程的协调,有三种方式(我现在只知道3种),分别是通过创建互斥对象函数(CreateMutex)、创建事件对象函数(CreateEvent)、初始化临界区对象函数(InitializeCriticalSection)来实现。
代码1(创建互斥对象函数):
- #include <windows.h>
- #include <iostream.h>
- int tickets=100; //共享资源
- HANDLE hMutex; //共享对象(因为是全局变量嘛~)
- //线程入口函数的声明
- DWORD WINAPI Func1Proc(LPVOID lpParameter);
- DWORD WINAPI Func2Proc(LPVOID lpParameter);
- //主线程
- int main()
- {
- HANDLE hThread1;
- HANDLE hThread2;
- /*安全属性(SecurityAttributes),
- *是否拥有互斥对象(InitialOwner),
- *是否是匿名的互斥对象(MutexName)
- */
- hMutex=CreateMutex(NULL,FALSE,NULL);
- hThread1=CreateThread(NULL,0,Func1Proc,NULL,0,NULL);
- hThread2=CreateThread(NULL,0,Func2Proc,NULL,0,NULL);
- CloseHandle(hThread1);
- CloseHandle(hThread2);
- Sleep(4000);
- return 0;
- }
- //线程1的入口函数
- DWORD WINAPI Func1Proc(LPVOID lpParameter)
- {
- while(true)
- {
- WaitForSingleObject(hMutex,INFINITE);
- if(tickets>0)
- {
- cout<<"thread1 sell tickets: "<<tickets--<<endl; //tickets先输出,再减1
- }
- else
- {
- break;
- }
- ReleaseMutex(hMutex);
- }
- return 0;
- }
- //线程2的入口函数
- DWORD WINAPI Func2Proc(LPVOID lpParameter)
- {
- while(true)
- {
- WaitForSingleObject(hMutex,INFINITE);
- if(tickets>0)
- {
- cout<<"thread2 sell tickets: "<<tickets--<<endl; //tickets先输出,再减1
- }
- else
- {
- break;
- }
- ReleaseMutex(hMutex);
- }
- return 0;
- }
需要注意的是,CreateMutex函数的3个参数,说明如下:
HANDLE CreateMutex( LPSECURITY_ATTRIBUTES , BOOL , LPCTSTR );
第一个参数,表示默认的安全属性;
第二个参数,表示创建互斥对象时主线程是否拥有互斥对象;
第三个参数,互斥对象名
代码2(创建事件对象函数):
- #include <windows.h>
- #include <iostream.h>
- int tickets=100; //共享资源
- HANDLE hEvent; //共享对象(因为是全局变量嘛~)
- //线程入口函数的声明
- DWORD WINAPI Func1Proc(LPVOID lpParameter);
- DWORD WINAPI Func2Proc(LPVOID lpParameter);
- //主线程
- int main()
- {
- HANDLE hThread1;
- HANDLE hThread2;
- /*第三个参数FALSE,表示事件对象初始化为无信号状态
- *第二个参数FASLE,表示线程申请事件对象之后(自动设置为无信号状态)
- */
- hEvent=CreateEvent(NULL,FALSE,TRUE,NULL);
- hThread1=CreateThread(NULL,0,Func1Proc,NULL,0,NULL);
- hThread2=CreateThread(NULL,0,Func2Proc,NULL,0,NULL);
- CloseHandle(hThread1);
- CloseHandle(hThread2);
- Sleep(4000);
- return 0;
- }
- //线程1的入口函数
- DWORD WINAPI Func1Proc(LPVOID lpParameter)
- {
- while(true)
- {
- WaitForSingleObject(hEvent,INFINITE);
- if(tickets>0)
- {
- cout<<"thread1 sell tickets: "<<tickets--<<endl; //tickets先输出,再减1
- SetEvent(hEvent);
- }
- else
- {
- break;
- }
- }
- return 0;
- }
- //线程2的入口函数
- DWORD WINAPI Func2Proc(LPVOID lpParameter)
- {
- while(true)
- {
- WaitForSingleObject(hEvent,INFINITE);
- if(tickets>0)
- {
- cout<<"thread2 sell tickets: "<<tickets--<<endl; //tickets先输出,再减1
- SetEvent(hEvent);
- }
- else
- {
- break;
- }
- }
- return 0;
- }
需要注意的是,CreateMutex函数的4个参数,说明如下:
HANDLE CreateEvent( LPSECURITY_ATTRIBUTES , BOOL , BOOL , LPTSTR );
第一个参数,同上;
第二个参数,表示申请到线程互斥对象后是否手动设置为无信号状态;
第三个参数,表示事件对象的初始化状态;(更正:代码中的注释行,应该是初始化为有信号状态)
第四个参数,同上
代码3(初始化临界区对象函数):
- #include <windows.h>
- #include <iostream.h>
- int tickets=100; //共享资源
- CRITICAL_SECTION g_cs; //共享对象(因为是全局变量嘛~)
- //线程入口函数的声明
- DWORD WINAPI Func1Proc(LPVOID lpParameter);
- DWORD WINAPI Func2Proc(LPVOID lpParameter);
- //主线程
- int main()
- {
- HANDLE hThread1;
- HANDLE hThread2;
- /*似乎,临界区对象最方便,至少初始化的时候就只需要一个参数
- *
- */
- InitializeCriticalSection(&g_cs);
- hThread1=CreateThread(NULL,0,Func1Proc,NULL,0,NULL);
- hThread2=CreateThread(NULL,0,Func2Proc,NULL,0,NULL);
- CloseHandle(hThread1);
- CloseHandle(hThread2);
- Sleep(4000);
- DeleteCriticalSection(&g_cs);
- return 0;
- }
- //线程1的入口函数
- DWORD WINAPI Func1Proc(LPVOID lpParameter)
- {
- while(true)
- {
- EnterCriticalSection(&g_cs);
- if(tickets>0)
- {
- cout<<"thread1 sell tickets: "<<tickets--<<endl; //tickets先输出,再减1
- }
- else
- {
- break;
- }
- LeaveCriticalSection(&g_cs);
- }
- return 0;
- }
- //线程2的入口函数
- DWORD WINAPI Func2Proc(LPVOID lpParameter)
- {
- while(true)
- {
- EnterCriticalSection(&g_cs);
- if(tickets>0)
- {
- cout<<"thread2 sell tickets: "<<tickets--<<endl; //tickets先输出,再减1
- }
- else
- {
- break;
- }
- LeaveCriticalSection(&g_cs);
- }
- return 0;
- }
我现在想说的是,如果综合起来看的话,对于第一种方法,需要通过WaitForSingleObject函数来申请互斥对象,一旦申请到后,(操作系统会将互斥对象设置为无信号状态),以防止其它线程申请。当售完票之后,该线程必须调用ReleaseMutex函数释放互斥对象,(操作系统又会将互斥对象设置为有信号状态)。这时,其它线程就可以申请互斥对象。如此循环,即可实现线程的协调。
对于第三种方法,原理一样,无非换成了EnterCriticalSection函数和LeaveCriticalSection函数。
而第二种方法,大致也是如此,只是换成了WaitForSingleObject函数和SetEvent函数。
但这几种线程协调的方法,肯定有不同之处(不然弄三个这玩意干嘛~),至于具体差别在哪儿,我也不是很清楚。因为书上也没有写清楚。