首先从一个简单的例子开始这部分内容,主线程启动10个线程,并将子线程的序号作为参数变量传递给线程函数。子线程接收参数后,依次sleep(50),nNum++,sleep(0),最终输出线程序号和全局变量。我们希望线程序号不重复,且全局变量的输出必须递增

一、分析
1、主线程创建子线程并创建一个指向变量地址的指针作为参数,由于线程启动需要一定时间,所以要等指针传入子线程,待子线程保存序号后才能递增序号值,启动下一线程。——主线程和子线程同步;
2、子线程之间需要互斥地改动和访问全局变量,全局变量要实现递增。——子线程之间的互斥。

一般我们会这样写程序:

    #include <stdio.h>
    #include <process.h>
    #include <windows.h>
    long g_nNum;        
    unsigned int _stdcall Fun(void *pm);    
    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++;
        }
 WaitForMultipleObjects(Thread_num,handle,TRUE,INFINITE);
        return 0;
    }
    unsigned int _stdcall Fun(void *pm)
    {
        int nThreadnum=*(int *)pm;            
        Sleep(50);                            
        g_nNum++;                            
        Sleep(0);                            
        printf("线程编号为%d,全局资源为%d\n",nThreadnum,g_nNum);
        return 0;
    }

但是运行结果不是我们想要的:
请输入图片描述

二、关键段CS
关键段CRITICAL_SECTION一共四个函数:
1、void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection); 定义关键段之后初始化;
2、void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection); 销毁关键段;
3、void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection); 进入关键段,保证线程互斥的进入关键段;
4、void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection); 离开关键段区域;

在上面程序中设置两个关键段,一个是主线程递增线程序号,另一个是子线程互斥的访问全局变量。

#include <stdio.h>
#include <process.h>
#include <windows.h>
long g_nNum;        
unsigned int _stdcall Fun(void *pm);    
const int Thread_num=10;            
//关键段声明
CRITICAL_SECTION ThreadParameter,ThreadCode;

int main()
{
    g_nNum=0;
    HANDLE handle[Thread_num];
    int i=0;
    //关键段初始化
    InitializeCriticalSection(&ThreadParameter);
    InitializeCriticalSection(&ThreadCode);
    while(i<Thread_num)
    {
        //进入子线程序号关键段
        EnterCriticalSection(&ThreadParameter);
        handle[i]=(HANDLE)_beginthreadex(NULL,0,Fun,&i,0,NULL);
        i++;
    }
    WaitForMultipleObjects(Thread_num,handle,TRUE,INFINITE);
    //销毁关键段
    DeleteCriticalSection(&ThreadParameter);
    DeleteCriticalSection(&ThreadCode);
    return 0;
}
unsigned int _stdcall Fun(void *pm)
{
    int nThreadnum=*(int *)pm;            

    //待序号保存完毕,离开子线程序号关键段
    LeaveCriticalSection(&ThreadParameter);

    Sleep(50);        
    //进入各子线程互斥区域
    EnterCriticalSection(&ThreadCode);
    g_nNum++;                            
    Sleep(0);                            
    printf("线程编号为%d,全局资源为%d\n",nThreadnum,g_nNum);
    //离开各子线程互斥区域
    LeaveCriticalSection(&ThreadCode);
    return 0;
}

运行如下:
请输入图片描述

看上去好像已经实现了序号不重复、全局资源递增,但是子线程和主线程的同步出现了问题,多次试验会发现线程序号还存在重复。主要原因是主线程可以自由出入关键段,在子线程接受参数之前已经写改了线程序号值,关键段可以用于线程间的互斥,但不可以用于同步。

三、旋转锁
由于将线程切换到等待状态的开销较大,因此为了提高关键段的性能,Microsoft将旋转锁合并到关键段中,这样EnterCriticalSection()会先用一个旋转锁不断循环,尝试一段时间才会将线程切换到等待状态。
1、`InitializeCriticalSectionAndSpinCount(
LPCRITICAL_SECTIONlpCriticalSection,DWORDdwSpinCount);`旋转次数一般设置为4000;
2、`DWORDSetCriticalSectionSpinCount(
LPCRITICAL_SECTIONlpCriticalSection,DWORDdwSpinCount);`
修改关键段的旋转次数。

标签: none

评论已关闭