4:一个经典的多线程同步问题汇总
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
一个经典的多线程同步问题
程序描述:
主线程启动10个子线程并将表示子线程序号的变量地址作为参数传递给子线程。子线程接收参数 -> sleep(50) -> 全局变量++ -> sleep(0) -> 输出参数和全局变量。
要求:
1.子线程输出的线程序号不能重复。
2.全局变量的输出必须递增。
下面画了个简单的示意图:
分析下这个问题的考察点,主要考察点有二个:
1.主线程创建子线程并传入一个指向变量地址的指针作参数,由于线程启动须要花费一定的时间,所以在子线程根据这个指针访问并保存数据前,主线程应等待子线程保存完毕后才能改动该参数并启动下一个线程。这涉及到主线程与子线程之间的同步。
2.子线程之间会互斥的改动和输出全局变量。要求全局变量的输出必须递增。这涉及到各子线程间的互斥。
下面列出这个程序的基本框架,可以在此代码基础上进行修改和验证。
//经典线程同步互斥问题
#include
#include
#include
long g_nNum; //全局资源
unsigned int__stdcall Fun(void *pPM); //线程函数
const int THREAD_NUM = 10; //子线程个数
int main()
{
g_nNum = 0;
HANDLE handle[THREAD_NUM];
int i = 0;
while (i < THREAD_NUM)
{
handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);
i++;//等子线程接收到参数时主线程可能改变了这个i的值}
//保证子线程已全部运行结束
WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
return 0;
}
unsigned int__stdcall Fun(void *pPM)
{
//由于创建线程是要一定的开销的,所以新线程并不能第一时间执行到这来int nThreadNum = *(int *)pPM; //子线程获取参数
Sleep(50);//some work should to do
g_nNum++; //处理全局资源
Sleep(0);//some work should to do
printf("线程编号为%d 全局资源值为%d\n", nThreadNum, g_nNum);
return 0;
}
运行结果:
可以看出,运行结果完全是混乱和不可预知的。运用Windows平台下各种手段包括关键段,事件,互斥量,信号量等等来解决这个问题。
关键段CRITICAL_SECTION
首先介绍下如何使用关键段,然后再深层次的分析下关键段的实现机制与原理。
关键段CRITICAL_SECTION一共就四个函数,使用很是方便。下面是这四个函数的原型和使用说明。
函数功能:初始化
函数原型:
void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
函数说明:定义关键段变量后必须先初始化。
函数功能:销毁
函数原型:
void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
函数说明:用完之后记得销毁。
函数功能:进入关键区域
函数原型:
void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
函数说明:系统保证各线程互斥的进入关键区域。
函数功能:离开关关键区域
函数原型:
void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
然后在经典多线程问题中设置二个关键区域。一个是主线程在递增子线程序号时,另一个是各子线程互斥的访问输出全局资源时。详见代码:
#include
#include
#include
long g_nNum;
unsigned int__stdcall Fun(void *pPM);
const int THREAD_NUM = 10;
//关键段变量声明
CRITICAL_SECTION g_csThreadParameter, g_csThreadCode;
int main()
{
printf("经典线程同步--关键段\n");
//关键段初始化
InitializeCriticalSection(&g_csThreadParameter);
InitializeCriticalSection(&g_csThreadCode);
HANDLE handle[THREAD_NUM];
g_nNum = 0;
int i = 0;
while (i < THREAD_NUM)
{
EnterCriticalSection(&g_csThreadParameter);//进入子线程序号关键区域
handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);
++i;
}
WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
DeleteCriticalSection(&g_csThreadCode);
DeleteCriticalSection(&g_csThreadParameter);
return 0;
}
unsigned int__stdcall Fun(void *pPM)
{
int nThreadNum = *(int *)pPM;
LeaveCriticalSection(&g_csThreadParameter);//离开子线程序号关键区域
Sleep(50);//some work should to do
EnterCriticalSection(&g_csThreadCode);//进入各子线程互斥区域
g_nNum++;
Sleep(0);//some work should to do
printf("线程编号为%d 全局资源值为%d\n", nThreadNum, g_nNum);