您的位置:首页 > 其它

多线程第三篇:关键段(临界区)

2013-12-15 19:01 302 查看
线程之间同步于互斥:

我们先来做一道题:
问题描述:
1.主线程启动10个子线程并将表示子线程序号的变量地址作为参数传递给子线程。
2.子线程接收参数 -> sleep(50) -> 全局变量++
-> sleep(0) -> 输出参数和全局变量。

要求:

1.子线程输出的线程序号不能重复。

2.全局变量的输出必须递增。

对程序的第一步改进:
#include <iostream>
#include <windows.h>
#include <process.h>

int g_count = 0;
unsigned int __stdcall ThreadFun(void *pthread_num)
{
Sleep(50);
int num = *(( int*)pthread_num );
std::cout<< "线程编号为:" <<num<< "全局资源编号为:" <<++g_count<< std::endl ;
Sleep(0);
return 0;
}

int main ()
{
const int thread_num = 10;
HANDLE handle[thread_num ];

for ( int i = 0 ; i<thread_num ; ++i ){
handle[i ] = (HANDLE) _beginthreadex(NULL ,0,ThreadFun,& i,0,NULL );
}

//等待所有创建的子线程都执行完.
WaitForMultipleObjects( thread_num,handle ,true,INFINITE);

return 0;
}

好了让我们看一看结果:(惨不忍睹呀!!!)



程序到底做了什么,这是一坨神马玩意????!!!!!!!!oh,no.
我们来分析一下到底为什么产生这种结果:
1.我们没有对输出进行原子控制,即打印语句不可中断.
2.我们没有控制线程的执行顺序,线程每次总是随机执行,并且执行期间可能被中断.

下面关键段出场了.
关键段一共四个函数:
1.初始化函数:(在使用关键段前必须初始化)
void InitializeCriticalSection (LPCRITICAL_SECTION lpCriticalSection);

2.销毁关键段:(使用完关键段后必须销毁,以免造成资源泄露)
void DeleteCriticalSection( LPCRITICAL_SECTION lpCriticalSection);

3.进入关键区:(保证各个线程之间互斥进入,但是主线程例外,主线程可以随时进入关键段)
void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

我们来编一段代码,使用关键段:
#include <iostream>
#include <windows.h>
#include <process.h>

int g_count = 0;
CRITICAL_SECTION g_csThreadParameter, g_csThreadCode;
unsigned int __stdcall ThreadFun(void *pthread_num)
{
Sleep(50);
int num = *(( int*)pthread_num );
LeaveCriticalSection(& g_csThreadParameter);

EnterCriticalSection(& g_csThreadCode);
std::cout<< "线程号:" <<GetCurrentThreadId()<< "线程编号为地址:" <<&num<< "线程编号" <<num<< "全局资源编号为:" <<++g_count<< std::endl ;
LeaveCriticalSection(& g_csThreadCode);
Sleep(0);
return 0;
}

int main ()
{
InitializeCriticalSection(& g_csThreadParameter);
InitializeCriticalSection(& g_csThreadCode);
const int thread_num = 10;
HANDLE handle[thread_num ];

for ( int i = 0 ; i<thread_num ; ++i ){
EnterCriticalSection(&g_csThreadParameter );
handle[i ] = (HANDLE) _beginthreadex(NULL ,0,ThreadFun,& i,0,NULL );
}

//等待所有创建的子线程都执行完.
WaitForMultipleObjects( thread_num,handle ,true,INFINITE);

return 0;
}

结果:



终于输出顺序输出1-10了,但是我们的线程编号为什么都是10呀,
这里我们解释一下:
1.关键段控制线程互斥的,主线程可以随时随地进入关键段,不受限制.
2.主线程创建线程和执行线程是分开的.即先创建,可能立即执行也肯能一段时间后在执行,这个是随机的.
3.不同关键段之间是不互斥的,如果线程A正在关键段M中执行,忽然cpu调度,执行线程B,此时线程B是不可以进入关键段M的,但是线程B可以进入除了关键段M之外的任意部分,如线程B进入了关键段N,或者线程B没进入关键段,只是执行没在关键段之中的代码.

上面有一处小小的不足,不知道大家看出来没.
就是main函数中的EnterCriticalSection .他并没起任何作用,我们用程序来验证一下:
#include <iostream>
#include <windows.h>
#include <process.h>

int g_count = 0;
CRITICAL_SECTION g_csThreadParameter, g_csThreadCode;
unsigned int __stdcall ThreadFun(void *pthread_num)
{
Sleep(50);
//EnterCriticalSection(&g_csThreadParameter);
int num = *(( int*)pthread_num );
std::cout<< "线程号:" <<GetCurrentThreadId()<< "线程编号" <<num<< std::endl ;
std::cout<<111<< std::endl ;
LeaveCriticalSection(& g_csThreadParameter);

EnterCriticalSection(& g_csThreadCode);
std::cout<< "线程号:" <<GetCurrentThreadId()<< "线程编号为地址:" <<&num<< "线程编号" <<num<< "全局资源编号为:" <<++g_count<< std::endl ;
LeaveCriticalSection(& g_csThreadCode);
Sleep(0);
return 0;
}

int main ()
{
InitializeCriticalSection(& g_csThreadParameter);
InitializeCriticalSection(& g_csThreadCode);
const int thread_num = 10;
HANDLE handle[thread_num ];

for ( int i = 0 ; i<thread_num ; ++i ){
EnterCriticalSection(&g_csThreadParameter );
handle[i ] = (HANDLE) _beginthreadex(NULL ,0,ThreadFun,& i,0,NULL );
}

//等待所有创建的子线程都执行完.
WaitForMultipleObjects( thread_num,handle ,true,INFINITE);

return 0;
}




我们可以清楚的看到,EnterCriticalSection (&g_csThreadParameter )并没有使下面这段程序互斥,所以我们一定要切记,控制线程的关键段(临界区)一定要在线程函数内部使用.
Sleep(50);
//EnterCriticalSection(&g_csThreadParameter);
int num = *(( int *)pthread_num );
std:: cout<< "线程号:" << GetCurrentThreadId()<< "线程编号" << num<< std ::endl ;
std:: cout<<111<< std ::endl ;
LeaveCriticalSection(& g_csThreadParameter );


我们再进一步改进代码:

#include <iostream>
#include <windows.h>
#include <process.h>

int g_count = 0;
CRITICAL_SECTION g_csThreadParameter, g_csThreadCode;
unsigned int __stdcall ThreadFun(void *pthread_num)
{
Sleep(50);
EnterCriticalSection(& g_csThreadParameter);
int num = *(( int*)pthread_num );
std::cout<< "线程号:" <<GetCurrentThreadId()<< "线程编号" <<num<< std::endl ;
std::cout<<111<< std::endl ;
LeaveCriticalSection(& g_csThreadParameter);

EnterCriticalSection(& g_csThreadCode);
std::cout<< "线程号:" <<GetCurrentThreadId()<< "线程编号为地址:" <<&num<< "线程编号" <<num<< "全局资源编号为:" <<++g_count<< std::endl ;
LeaveCriticalSection(& g_csThreadCode);
Sleep(0);
return 0;
}

int main ()
{
InitializeCriticalSection(& g_csThreadParameter);
InitializeCriticalSection(& g_csThreadCode);
const int thread_num = 10;
HANDLE handle[thread_num ];

for ( int i = 0 ; i<thread_num ; ++i ){
// EnterCriticalSection(&g_csThreadParameter);
handle[i ] = (HANDLE) _beginthreadex(NULL ,0,ThreadFun,& i,0,NULL );
}

//等待所有创建的子线程都执行完.
WaitForMultipleObjects( thread_num,handle ,true,INFINITE);

return 0;
}




我们更深入的探讨一下临界区应该怎么用.

我们再来看一段代码:
#include <iostream>
#include <windows.h>
#include <process.h>

int g_count = 0;

CRITICAL_SECTION producer_critical, consumer_critical ;
HANDLE producer_semaphore , consumer_semaphore;

CRITICAL_SECTION all;
HANDLE hevent ;

unsigned int __stdcall producer (void *)
{

EnterCriticalSection(& all );

std ::cout <<111<< std:: endl;
SetEvent( hevent );

return 0;
}

unsigned int __stdcall consumer (void *)
{
WaitForSingleObject( hevent ,INFINITE );
LeaveCriticalSection(& all );
EnterCriticalSection(& all );

std ::cout <<222<< std:: endl;

return 0;
}

int main ()
{

InitializeCriticalSection(& all );
hevent = CreateEvent (NULL , false, false, NULL);
HANDLE hproducer = (HANDLE ) _beginthreadex( NULL ,0,producer , NULL,0, NULL );
HANDLE hconsumer = (HANDLE ) _beginthreadex( NULL ,0,consumer , NULL,0, NULL );
WaitForSingleObject( hproducer ,INFINITE );
WaitForSingleObject( hconsumer ,INFINITE );
CloseHandle( hproducer );
CloseHandle( hconsumer );

return 0;
}


[align=left]结果:[/align]



[align=left]代码很好理解,用event控制线程producer先执行,consumer后执行.[/align]
[align=left]但是代码在producer中进入临界区,在consumer离开临界区,证明了,临界区并没有谁进入谁离开的限制,即在线程a进入在线程b离开也是可以的.[/align]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: