互斥對象、事件對象、關鍵代碼段編程
1、互斥對象和事件對象屬於內核對象,利用內核對象進行線程同步,速度較慢,但能夠實如今多個進程中各線程間進行同步。數據結構
2、關鍵代碼段式工做在用戶方式下,同步速度較快,但在使用關鍵代碼段時,很容易進入死鎖狀態,由於在進入關鍵代碼時沒法設定超時值。多線程
MFC下InitializeCriticalSection()和DeleteCriticalSection()能夠放在類的構造函數和析構函數中函數
在編寫程序時首選關鍵代碼段,但須要很是注意死鎖問題!spa
多線程編程推薦書籍《Windows核心編程》機械工業出版社操作系統
3、線程、進程、程序的區別線程
程序對象 |
計算機指令的集合,它以文件的形式存儲在磁盤上進程 |
進程事件 |
一般被定義爲一個正在運行的程序的實例,是一個程序在其自身的地址空間中的一次執行活動 |
區別:進程是資源申請、調度和獨立運行的單位,所以,它使用系統中的運行資源;而程序不能申請系統資源,不能被系統調度,也不能做爲獨立的運行的單位,所以,他不佔用系統的運行資源。
進程由兩個部分組成:
一、操做系統用來管理進程的內核對象。內核對象是操做系統內部分配的一個內存塊,內核對象也是系統用來存放關於進程的統計信息的地方。
二、地址空間。它包含全部可執行模塊或DLL模塊的代碼和數據。他還包含動態內存分配的空間。如線程堆棧和堆分配空間。
內核對象:是操做系統內部分配的一個內存塊,它是一種只能被內核訪問的數據結構,其成員負責維護該對象的各類信息,應用程序沒法找到並直接改變它們的內容,只能經過Windows提供的函數對內核對象進行操做。
進程
進程是不活潑的。進程歷來不執行任何東西,它只是線程的容器。
若要使進程完成某項操做,它必須擁有一個在它的環境中運行的線程,此線程負責執行包含在進程的地址空間中的代碼。
單個進程可能包含若干個線程,這些線程都「同時」執行進程地址空間中的代碼。
每一個進程至少擁有一個線程,來執行進程的地址空間中的代碼。
當建立一個進程時,操做系統會自動建立這個進程的一個線程,稱爲主線程。此後,該線程能夠建立其餘的線程
線程
線程有兩個部分組成:
1。線程的內核對象,操做系統用它來對線程實施管理,內核對象也是系統用來存放線程統計信息的地方。
2。線程堆棧,它用於維護線程在執行代碼時須要的全部參數和局部變量。
當建立線程時,系統建立一個線程內核對象。
該線程內核對象不是線程自己,而是操做系統用來管理線程的較小的數據結構。
能夠將線程內核對象視爲由關於線程的統計信息組成的一個小型數據結構。
線程老是在某個進程環境中建立。
系統從進程的地址空間中分配內存,供線程的堆棧使用。
新線程運行的進程環境與建立線程的環境相同。
所以,新線程能夠訪問進程的內核對象的全部句柄、進程中的全部內存和在這個相同的進程中的全部其餘線程的堆棧。這使得單個進程中的多個線程確實可以很是容易的互相通訊。
線程只有一個內核對象和一個堆棧,保留的記錄不多,所以所須要的內存也不多。
由於線程須要的開銷比進程少,所以在編程中常常採用多線程來解決編程問題,而儘可能避免建立新的進程。
線程運行
對於單個CPU
操做系統爲每個運行線程安排必定的CPU時間——時間片。
系統經過一種循環的方式爲線程提供時間片,線程在本身的時間內運行,因時間片至關短,所以,給用戶的感受,就好像線程是同時進行的同樣。
若是計算機擁有多個CPU,線程就能真正意義上運行了
注意
咱們能夠用多進程代替多線程,可是這樣不是明智的,由於
1.每新建一個進程,系統要爲之分配4GB的虛擬內存,浪費資源;而多線程共享同一個地址空間,佔用資源較少
2.在進程之間發生切換時,要交換整個地址空間;而線程之間的切換隻是執行環境的改變,效率較高。
4、多線程具體實現
1、互斥對象
這種辦法不適合在多核CPU的電腦上 。自動重置(只有一個線程能夠獲得事件對象)
#include
#include
DWORD WINAPI Fun1Proc (LPVOID lpParameter);
DWORD WINAPI Fun2Proc (LPVOID lpParameter);
int index=0;
int tickets=100;
HANDLE hMutex;
void main()
{
HANDLE hThread1,hThread2;
hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(hThread1);//關閉句柄沒有終止線程,標示主線程對新線程的引用不感興趣,引用計數減1
CloseHandle(hThread2);
//建立互斥對象
hMutex=CreateMutex(NULL,FALSE,NULL);//FALSE標示該線程(main)不得到互斥對象的全部權
Sleep(4000);
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)//當引用計數爲0時,終止線程
{
while (TRUE)
{
WaitForSingleObject(hMutex,INFINITE); //INFINITE表示一直等待hMutex有效,不超時退出
if (tickets>0)
{
Sleep(2);
cout<<"thread1 sell tickes :"<<tickets--<<endl;
}
else
{
break;
}
ReleaseMutex(hMutex);
}
return 0;
}
DWORD WINAPI Fun2Proc (LPVOID lpParameter)
{
while (TRUE)
{
WaitForSingleObject(hMutex,INFINITE);
if (tickets>0)
{
cout<<"thread2 sell tickes :"<<tickets--<<endl;
}
else
{
break;
}
ReleaseMutex(hMutex);
}
return 0;
}
2、事件對象
這種辦法不適合在多核CPU的電腦上 。自動重置(只有一個線程能夠獲得事件對象)
#include
#include
DWORD WINAPI Fun1Proc (LPVOID lpParameter);
DWORD WINAPI Fun2Proc (LPVOID lpParameter);
int tickets=100;
HANDLE g_hEvent;
void main()
{
HANDLE hThread1,hThread2;
HANDLE g_hEvent1;
g_hEvent1=CreateEvent(NULL,FALSE,FALSE,"tickets");//讓應用程序只能有一個實例
if(g_hEvent1)
{
if(ERROR_ALREADY_EXISTS==GetLastError())
{
cout<<"only one instance can run !"<<endl;
return;
}
}
g_hEvent=CreateEvent(NULL,FALSE,FALSE,NULL);//建立自動重置事件內核對象,非信號狀態
hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(hThread1);//關閉句柄沒有終止線程,標示主線程對新線程的引用不感興趣,引用計數減1
CloseHandle(hThread2);
SetEvent(g_hEvent);
Sleep(4000);
CloseHandle(g_hEvent);
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)//當引用計數爲0時,終止線程
{
while (TRUE)
{
WaitForSingleObject(g_hEvent,INFINITE);
if (tickets>0)
{
Sleep(2);
cout<<"thread1 sell tickes :"<<tickets--<<endl;
}
else
{
break;
}
SetEvent(g_hEvent);
}
return 0;
}
DWORD WINAPI Fun2Proc (LPVOID lpParameter)
{
while (TRUE)
{
WaitForSingleObject(g_hEvent,INFINITE);
if (tickets>0)
{
cout<<"thread2 sell tickes :"<<tickets--<<endl;
}
else
{
break;
}
SetEvent(g_hEvent);
}
return 0;
}
3、關鍵代碼段
又稱爲臨界區:對資源的獨佔權。
初始化臨界區對象函數:InitializeCriticalSection(EnterCriticalSection(LPCRITICAL_SECTION))參數爲臨界區對象;
申請臨界區函數:EnterCriticalSection(LPCRITICAL_SECTION)參數爲臨界區對象;
釋放臨界區函數:LeaveCriticalSection(LPCRITICAL_SECTION)參數爲臨界區對象;
釋放臨界區資源:DeleteCriticalSection(&g_cs);
使用關鍵代碼段的線程同步代碼:
#include
#include
#include
DWORD WINAPI Fun1Proc (LPVOID lpParameter);
DWORD WINAPI Fun2Proc (LPVOID lpParameter);
int tickets=100;
CRITICAL_SECTION g_cs;
void main()
{
HANDLE hThread1,hThread2;
HANDLE g_hEvent1;
g_hEvent1=CreateEvent(NULL,FALSE,FALSE,"tickets");//讓應用程序只能有一個實例
if(g_hEvent1)
{
if(ERROR_ALREADY_EXISTS==GetLastError())
{
cout<<"only one instance can run !"<<endl;
return;
}
}
InitializeCriticalSection(&g_cs);//必須在建立線程以前初始化臨界區對象,不然會出錯
//g_hEvent=CreateEvent(NULL,FALSE,FALSE,NULL);//建立自動重置事件內核對象,非信號狀態
hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(hThread1);//關閉句柄沒有終止線程,標示主線程對新線程的引用不感興趣,引用計數減1
CloseHandle(hThread2);
//SetEvent(g_hEvent);
//getchar();
Sleep(4000);
DeleteCriticalSection(&g_cs);
getchar();
//CloseHandle(g_hEvent);
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)//當引用計數爲0時,終止線程
{
while (TRUE)
{
EnterCriticalSection(&g_cs);
if (tickets>0)
{
Sleep(2);
cout<<"thread1 sell tickes :"<<tickets--<<endl;
LeaveCriticalSection(&g_cs);
}
else
{
LeaveCriticalSection(&g_cs);
break;
}
}
return 0;
}
DWORD WINAPI Fun2Proc (LPVOID lpParameter)
{
while (TRUE)
{
EnterCriticalSection(&g_cs);
if (tickets>0)
{
cout<<"thread2 sell tickes :"<<tickets--<<endl;
LeaveCriticalSection(&g_cs);
}
else
{
LeaveCriticalSection(&g_cs);
break;
}
}
return 0;
}
3、線程死鎖問題:
#include
#include
#include
DWORD WINAPI Fun1Proc (LPVOID lpParameter);
DWORD WINAPI Fun2Proc (LPVOID lpParameter);
int tickets=100;
CRITICAL_SECTION g_csA;
CRITICAL_SECTION g_csB;
void main()
{
HANDLE hThread1,hThread2;
HANDLE g_hEvent1;
g_hEvent1=CreateEvent(NULL,FALSE,FALSE,"tickets");//讓應用程序只能有一個實例
if(g_hEvent1)
{
if(ERROR_ALREADY_EXISTS==GetLastError())
{
cout<<"only one instance can run !"<<endl;
return;
}
}
InitializeCriticalSection(&g_csA);
InitializeCriticalSection(&g_csB);
//g_hEvent=CreateEvent(NULL,FALSE,FALSE,NULL);//建立自動重置事件內核對象,非信號狀態
hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(hThread1);//關閉句柄沒有終止線程,標示主線程對新線程的引用不感興趣,引用計數減1
CloseHandle(hThread2);
//SetEvent(g_hEvent);
//getchar();
Sleep(4000);
DeleteCriticalSection(&g_csA);
DeleteCriticalSection(&g_csB);
//CloseHandle(g_hEvent);
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)//當引用計數爲0時,終止線程
{
while (TRUE)
{
EnterCriticalSection(&g_csA);
//Sleep(1);
EnterCriticalSection(&g_csB);
if (tickets>0)
{
Sleep(2);
cout<<"thread1 sell tickes :"<<tickets--<<endl;
LeaveCriticalSection(&g_csB);
LeaveCriticalSection(&g_csA);
}
else
{
LeaveCriticalSection(&g_csB);
LeaveCriticalSection(&g_csA);
break;
}
}
return 0;
}
DWORD WINAPI Fun2Proc (LPVOID lpParameter)
{
while (TRUE)
{
EnterCriticalSection(&g_csB);
//Sleep(1);
EnterCriticalSection(&g_csA);
if (tickets>0)
{
cout<<"thread2 sell tickes :"<<tickets--<<endl;
LeaveCriticalSection(&g_csA);
LeaveCriticalSection(&g_csB);
}
else
{
LeaveCriticalSection(&g_csA);
LeaveCriticalSection(&g_csB);
break;
}
}
return 0;
}
1、互斥對象和事件對象屬於內核對象,利用內核對象進行線程同步,速度較慢,但能夠實如今多個進程中各線程間進行同步。
2、關鍵代碼段式工做在用戶方式下,同步速度較快,但在使用關鍵代碼段時,很容易進入死鎖狀態,由於在進入關鍵代碼時沒法設定超時值。
MFC下InitializeCriticalSection()和DeleteCriticalSection()能夠放在類的構造函數和析構函數中
在編寫程序時首選關鍵代碼段,但須要很是注意死鎖問題!
多線程編程推薦書籍《Windows核心編程》機械工業出版社
3、線程、進程、程序的區別
程序 |
計算機指令的集合,它以文件的形式存儲在磁盤上 |
進程 |
一般被定義爲一個正在運行的程序的實例,是一個程序在其自身的地址空間中的一次執行活動 |
區別:進程是資源申請、調度和獨立運行的單位,所以,它使用系統中的運行資源;而程序不能申請系統資源,不能被系統調度,也不能做爲獨立的運行的單位,所以,他不佔用系統的運行資源。
進程由兩個部分組成:
一、操做系統用來管理進程的內核對象。內核對象是操做系統內部分配的一個內存塊,內核對象也是系統用來存放關於進程的統計信息的地方。
二、地址空間。它包含全部可執行模塊或DLL模塊的代碼和數據。他還包含動態內存分配的空間。如線程堆棧和堆分配空間。
內核對象:是操做系統內部分配的一個內存塊,它是一種只能被內核訪問的數據結構,其成員負責維護該對象的各類信息,應用程序沒法找到並直接改變它們的內容,只能經過Windows提供的函數對內核對象進行操做。
進程
進程是不活潑的。進程歷來不執行任何東西,它只是線程的容器。
若要使進程完成某項操做,它必須擁有一個在它的環境中運行的線程,此線程負責執行包含在進程的地址空間中的代碼。
單個進程可能包含若干個線程,這些線程都「同時」執行進程地址空間中的代碼。
每一個進程至少擁有一個線程,來執行進程的地址空間中的代碼。
當建立一個進程時,操做系統會自動建立這個進程的一個線程,稱爲主線程。此後,該線程能夠建立其餘的線程
線程
線程有兩個部分組成:
1。線程的內核對象,操做系統用它來對線程實施管理,內核對象也是系統用來存放線程統計信息的地方。
2。線程堆棧,它用於維護線程在執行代碼時須要的全部參數和局部變量。
當建立線程時,系統建立一個線程內核對象。
該線程內核對象不是線程自己,而是操做系統用來管理線程的較小的數據結構。
能夠將線程內核對象視爲由關於線程的統計信息組成的一個小型數據結構。
線程老是在某個進程環境中建立。
系統從進程的地址空間中分配內存,供線程的堆棧使用。
新線程運行的進程環境與建立線程的環境相同。
所以,新線程能夠訪問進程的內核對象的全部句柄、進程中的全部內存和在這個相同的進程中的全部其餘線程的堆棧。這使得單個進程中的多個線程確實可以很是容易的互相通訊。
線程只有一個內核對象和一個堆棧,保留的記錄不多,所以所須要的內存也不多。
由於線程須要的開銷比進程少,所以在編程中常常採用多線程來解決編程問題,而儘可能避免建立新的進程。
線程運行
對於單個CPU
操做系統爲每個運行線程安排必定的CPU時間——時間片。
系統經過一種循環的方式爲線程提供時間片,線程在本身的時間內運行,因時間片至關短,所以,給用戶的感受,就好像線程是同時進行的同樣。
若是計算機擁有多個CPU,線程就能真正意義上運行了
注意
咱們能夠用多進程代替多線程,可是這樣不是明智的,由於
1.每新建一個進程,系統要爲之分配4GB的虛擬內存,浪費資源;而多線程共享同一個地址空間,佔用資源較少
2.在進程之間發生切換時,要交換整個地址空間;而線程之間的切換隻是執行環境的改變,效率較高。
4、多線程具體實現
1、互斥對象
這種辦法不適合在多核CPU的電腦上 。自動重置(只有一個線程能夠獲得事件對象)
#include
#include
DWORD WINAPI Fun1Proc (LPVOID lpParameter);
DWORD WINAPI Fun2Proc (LPVOID lpParameter);
int index=0;
int tickets=100;
HANDLE hMutex;
void main()
{
HANDLE hThread1,hThread2;
hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(hThread1);//關閉句柄沒有終止線程,標示主線程對新線程的引用不感興趣,引用計數減1
CloseHandle(hThread2);
//建立互斥對象
hMutex=CreateMutex(NULL,FALSE,NULL);//FALSE標示該線程(main)不得到互斥對象的全部權
Sleep(4000);
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)//當引用計數爲0時,終止線程
{
while (TRUE)
{
WaitForSingleObject(hMutex,INFINITE); //INFINITE表示一直等待hMutex有效,不超時退出
if (tickets>0)
{
Sleep(2);
cout<<"thread1 sell tickes :"<<tickets--<<endl;
}
else
{
break;
}
ReleaseMutex(hMutex);
}
return 0;
}
DWORD WINAPI Fun2Proc (LPVOID lpParameter)
{
while (TRUE)
{
WaitForSingleObject(hMutex,INFINITE);
if (tickets>0)
{
cout<<"thread2 sell tickes :"<<tickets--<<endl;
}
else
{
break;
}
ReleaseMutex(hMutex);
}
return 0;
}
2、事件對象
這種辦法不適合在多核CPU的電腦上 。自動重置(只有一個線程能夠獲得事件對象)
#include
#include
DWORD WINAPI Fun1Proc (LPVOID lpParameter);
DWORD WINAPI Fun2Proc (LPVOID lpParameter);
int tickets=100;
HANDLE g_hEvent;
void main()
{
HANDLE hThread1,hThread2;
HANDLE g_hEvent1;
g_hEvent1=CreateEvent(NULL,FALSE,FALSE,"tickets");//讓應用程序只能有一個實例
if(g_hEvent1)
{
if(ERROR_ALREADY_EXISTS==GetLastError())
{
cout<<"only one instance can run !"<<endl;
return;
}
}
g_hEvent=CreateEvent(NULL,FALSE,FALSE,NULL);//建立自動重置事件內核對象,非信號狀態
hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(hThread1);//關閉句柄沒有終止線程,標示主線程對新線程的引用不感興趣,引用計數減1
CloseHandle(hThread2);
SetEvent(g_hEvent);
Sleep(4000);
CloseHandle(g_hEvent);
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)//當引用計數爲0時,終止線程
{
while (TRUE)
{
WaitForSingleObject(g_hEvent,INFINITE);
if (tickets>0)
{
Sleep(2);
cout<<"thread1 sell tickes :"<<tickets--<<endl;
}
else
{
break;
}
SetEvent(g_hEvent);
}
return 0;
}
DWORD WINAPI Fun2Proc (LPVOID lpParameter)
{
while (TRUE)
{
WaitForSingleObject(g_hEvent,INFINITE);
if (tickets>0)
{
cout<<"thread2 sell tickes :"<<tickets--<<endl;
}
else
{
break;
}
SetEvent(g_hEvent);
}
return 0;
}
3、關鍵代碼段
又稱爲臨界區:對資源的獨佔權。
初始化臨界區對象函數:InitializeCriticalSection(EnterCriticalSection(LPCRITICAL_SECTION))參數爲臨界區對象;
申請臨界區函數:EnterCriticalSection(LPCRITICAL_SECTION)參數爲臨界區對象;
釋放臨界區函數:LeaveCriticalSection(LPCRITICAL_SECTION)參數爲臨界區對象;
釋放臨界區資源:DeleteCriticalSection(&g_cs);
使用關鍵代碼段的線程同步代碼:
#include
#include
#include
DWORD WINAPI Fun1Proc (LPVOID lpParameter);
DWORD WINAPI Fun2Proc (LPVOID lpParameter);
int tickets=100;
CRITICAL_SECTION g_cs;
void main()
{
HANDLE hThread1,hThread2;
HANDLE g_hEvent1;
g_hEvent1=CreateEvent(NULL,FALSE,FALSE,"tickets");//讓應用程序只能有一個實例
if(g_hEvent1)
{
if(ERROR_ALREADY_EXISTS==GetLastError())
{
cout<<"only one instance can run !"<<endl;
return;
}
}
InitializeCriticalSection(&g_cs);//必須在建立線程以前初始化臨界區對象,不然會出錯
//g_hEvent=CreateEvent(NULL,FALSE,FALSE,NULL);//建立自動重置事件內核對象,非信號狀態
hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(hThread1);//關閉句柄沒有終止線程,標示主線程對新線程的引用不感興趣,引用計數減1
CloseHandle(hThread2);
//SetEvent(g_hEvent);
//getchar();
Sleep(4000);
DeleteCriticalSection(&g_cs);
getchar();
//CloseHandle(g_hEvent);
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)//當引用計數爲0時,終止線程
{
while (TRUE)
{
EnterCriticalSection(&g_cs);
if (tickets>0)
{
Sleep(2);
cout<<"thread1 sell tickes :"<<tickets--<<endl;
LeaveCriticalSection(&g_cs);
}
else
{
LeaveCriticalSection(&g_cs);
break;
}
}
return 0;
}
DWORD WINAPI Fun2Proc (LPVOID lpParameter)
{
while (TRUE)
{
EnterCriticalSection(&g_cs);
if (tickets>0)
{
cout<<"thread2 sell tickes :"<<tickets--<<endl;
LeaveCriticalSection(&g_cs);
}
else
{
LeaveCriticalSection(&g_cs);
break;
}
}
return 0;
}
3、線程死鎖問題:
#include
#include
#include
DWORD WINAPI Fun1Proc (LPVOID lpParameter);
DWORD WINAPI Fun2Proc (LPVOID lpParameter);
int tickets=100;
CRITICAL_SECTION g_csA;
CRITICAL_SECTION g_csB;
void main()
{
HANDLE hThread1,hThread2;
HANDLE g_hEvent1;
g_hEvent1=CreateEvent(NULL,FALSE,FALSE,"tickets");//讓應用程序只能有一個實例
if(g_hEvent1)
{
if(ERROR_ALREADY_EXISTS==GetLastError())
{
cout<<"only one instance can run !"<<endl;
return;
}
}
InitializeCriticalSection(&g_csA);
InitializeCriticalSection(&g_csB);
//g_hEvent=CreateEvent(NULL,FALSE,FALSE,NULL);//建立自動重置事件內核對象,非信號狀態
hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(hThread1);//關閉句柄沒有終止線程,標示主線程對新線程的引用不感興趣,引用計數減1
CloseHandle(hThread2);
//SetEvent(g_hEvent);
//getchar();
Sleep(4000);
DeleteCriticalSection(&g_csA);
DeleteCriticalSection(&g_csB);
//CloseHandle(g_hEvent);
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)//當引用計數爲0時,終止線程
{
while (TRUE){EnterCriticalSection(&g_csA);//Sleep(1);EnterCriticalSection(&g_csB);if (tickets>0){Sleep(2);cout<<"thread1 sell tickes :"<<tickets--<<endl;LeaveCriticalSection(&g_csB);LeaveCriticalSection(&g_csA);}else{LeaveCriticalSection(&g_csB);LeaveCriticalSection(&g_csA);break;}}return 0;}DWORD WINAPI Fun2Proc (LPVOID lpParameter){while (TRUE){EnterCriticalSection(&g_csB);//Sleep(1);EnterCriticalSection(&g_csA);if (tickets>0){cout<<"thread2 sell tickes :"<<tickets--<<endl;LeaveCriticalSection(&g_csA);LeaveCriticalSection(&g_csB);}else{LeaveCriticalSection(&g_csA);LeaveCriticalSection(&g_csB);break;}}return 0;}