• 多线程编程示例1(结合实例)


    1.CreateThread与_beginthreadex

    #pragma once
    
    #include<cstdio>
    #include<Windows.h>
    #include<crtdbg.h>
    #include<process.h>
    
    //子线程函数
    DWORD WINAPI ThreadFun1(LPVOID pM)
    {
        printf("子线程的线程ID号为:%d
    Hello world!
    ",GetCurrentThreadId());
        return 0;
    }
    
    void fun1()
    {
        printf("简单多线程实例!
    
    ");
    
        /*
           CreateThread参数解析
           1:线程内核安全属性
           2:线程栈空间大小
           3:线程执行函数地址
           4:传给线程执行函数参数
           5:线程创建控制参数(CREATE_SUSPENDED)
           6:线程ID号
        */
        HANDLE handle = CreateThread(NULL, 0, ThreadFun1, NULL, 0, NULL);
        WaitForSingleObject(handle, INFINITE);
        CloseHandle(handle);
    }
    
    //设置计数全局变量
    int COUNT = 0;
    
    //子线程函数
    unsigned int _stdcall ThreadFun2(PVOID pM)
    {
        ++COUNT;
        printf("子线程的线程ID号为:%d,报数为%d
    Hello world!
    ", GetCurrentThreadId(),COUNT);
        return 0;
    }
    
    void fun2()
    {
        printf("简单多线程实例!
    
    ");
    
        const int THREAD_NUM = 5;
        HANDLE handle[THREAD_NUM];
    
        for (size_t i = 0; i < THREAD_NUM; i++)
        {
            handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun2, NULL, 0, NULL);
        }
        WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
    }
    
    int main(void)
    {
        //使用CreateThread
        //fun1();
    
        //使用_beginthreadex
        //推荐使用原因为:使用标准C运行库函数时,易发生race condition
        //使用_beginthreadex可以避免数据被其他线程篡改
        //更加合理的解释可以参考Win32多线程编程
        fun2();
    
        //检测内存泄漏
        _CrtDumpMemoryLeaks();
        return 0;
    }

    其中执行fun2结果为:(蛮有趣的,不加锁竟然这么直观)

    2.原子操作

    #pragma once
    
    #include<cstdio>
    #include<Windows.h>
    #include<crtdbg.h>
    #include<process.h>
    
    volatile long COUNT = 0;
    const int THREAD_NUM = 500;
    
    unsigned int _stdcall ThreadFun1(LPVOID pM)
    {
        Sleep(50);
        //++COUNT;
        InterlockedIncrement((LPLONG)&COUNT); //使用原子锁替换
        Sleep(50);
        return 0;
    }
    
    unsigned int _stdcall ThreadFun2(LPVOID pM)
    {
        Sleep(50);
        InterlockedIncrement((LPLONG)&COUNT); //使用原子锁替换
        printf("线程编号为%d,全局资源值为%d
    ", *(int *)pM, COUNT);
        return 0;
    }
    
    
    void fun1()
    {
        HANDLE handle[THREAD_NUM];
    
        for (size_t i = 0; i < THREAD_NUM; i++)
        {
            handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun1, NULL, 0, NULL);
        }
        WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
        printf("有%d个线程启动,记录结果为%d", THREAD_NUM, COUNT);
    }
    
    void fun2()
    {
        HANDLE handle[THREAD_NUM];
    
        for (size_t i = 0; i < THREAD_NUM; i++)
        {
            handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun2, &i, 0, NULL);
        }
        WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
    }
    
    int main()
    {
        //test1
        //fun1(); //易发现线程启动数和计数不匹配
    
        //test2
        fun2(); //线程编号和全局资源值
    
        //检测内存泄漏
        _CrtDumpMemoryLeaks();
        return 0;
    }

    fun1:这里++操作在汇编层面是分成三层的:(1)取值由内存存至寄存器;(2)寄存器中进行操作;(3)数值由寄存器转储至内存。这个过程容易出现问题。

    但是使用原子操作在只有50个线程启动时准确,但是上限调至500次时,线程启动数和计数又不一致(个人理解我inc虽然汇编下为一个操作,但是实际会分为多层次执行)。

    fun2:运行结果更为混乱,其中原子操作部分不表。

    线程ID不准确的原因是i在传递至子线程函数的过程中,在主线程中就已经被修改数值了。

    3.关键区域同步(CRITICAL_SECTION)

    #pragma once
    
    #include<cstdio>
    #include<Windows.h>
    #include<crtdbg.h>
    #include<process.h>
    
    unsigned int count = 0;
    const int threadnum = 50;
    CRITICAL_SECTION ThreadPar1, ThreadPar2;
    
    unsigned int _stdcall ThreadFun(LPVOID pM)
    {
        UINT num = *(UINT*)pM;
        //离开子线程ID号关键区域
        LeaveCriticalSection(&ThreadPar1);
    
        Sleep(50); //做点什么
    
        EnterCriticalSection(&ThreadPar2);
        ++count;
        printf("线程编号为%3d,全局资源值为%3d
    ", num, count);
        LeaveCriticalSection(&ThreadPar2);
    
        return 0;
    }
    
    
    int main()
    {
        InitializeCriticalSection(&ThreadPar1);
        InitializeCriticalSection(&ThreadPar2);
    
        HANDLE handle[threadnum];
    
        for (UINT i = 0; i < threadnum; i++)
        {
            //进入子线程ID号关键区域
            EnterCriticalSection(&ThreadPar1);
            handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, &i, 0, NULL);
        }
    
        WaitForMultipleObjects(threadnum, handle, TRUE, INFINITE);
        DeleteCriticalSection(&ThreadPar1);
        DeleteCriticalSection(&ThreadPar2);
    
        //检测内存泄漏
        _CrtDumpMemoryLeaks();
        return 0;
    }

    输出结果:(嘿嘿,ID号不正确,计数正确)

    出现此问题的原因在于ThreadPar1的线程所有权是主线程,而不是子线程,这也就导致其可以多次进入关键区域,继而导致ID号不同。

    而ThreadPar2的线程所有权是子线程,所以不出出现这个问题,输出正常。

    这也从侧面证明CRITICAL_SECTION无法解决同步问题,而只能解决互斥问题。

    4.事件(Event)

    #pragma once
    
    #define _CRTDBG_MAP_ALLOC
    #include<cstdio>
    #include<Windows.h>
    #include<crtdbg.h>
    #include<process.h>
    
    unsigned int count = 0;
    const int threadnum = 50;
    HANDLE ThreadEvent;
    CRITICAL_SECTION ThreadPar;
    
    unsigned int _stdcall ThreadFun1(LPVOID pM)
    {
        int num = *(int*)pM;
        SetEvent(ThreadEvent); //触发事件
    
        Sleep(50); //some work should to do
    
        EnterCriticalSection(&ThreadPar);
        ++count;
        printf("线程编号为%d,全局资源值为%d
    ", num, count);
        LeaveCriticalSection(&ThreadPar);
        return 0;
    }
    
    void fun1()
    {
        //初始化事件(自动置位,初始无触发的匿名事件)和关键段
        /*CreateEvent参数说明
          1:安全控制
          2:手动置位(true)/自动置位(false)
             自动置位,对事件调用WaitForSingleObject后,
             会自动调用ResetEvent使事件变为未触发状态
          3:事件初始状态(TRUE表示已触发)
          4:事件名称(NULL表示匿名)
        */
        ThreadEvent = CreateEvent(NULL, false, false, NULL);
        InitializeCriticalSection(&ThreadPar);
    
        size_t i;
        HANDLE handle[threadnum];
        for (i = 0; i < threadnum; i++)
        {
            handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun1, &i, 0, NULL);
            WaitForSingleObject(ThreadEvent, INFINITE);
        }
        WaitForMultipleObjects(threadnum, handle, TRUE, INFINITE);
    
        //销毁时间和关键段
        CloseHandle(ThreadEvent);
        DeleteCriticalSection(&ThreadPar);
        for ( i = 0; i < threadnum; i++)
        {
            CloseHandle(handle[i]);
        }
    }
    
    unsigned int _stdcall FastThreadFun(LPVOID pM)
    {
        Sleep(10); //以此来保证各线程调用等待函数的次序具有随机性
        printf("%s 启动
    ", (PSTR)pM);
        WaitForSingleObject(ThreadEvent, INFINITE);
        printf("%s 等到事件被触发,顺利结束
    ", (PSTR)pM);
        return 0;
    }
    
    unsigned int _stdcall SlowThreadFun(LPVOID pM)
    {
        Sleep(100); //以此来保证各线程调用等待函数的次序具有随机性
        printf("%s 启动
    ", (PSTR)pM);
        WaitForSingleObject(ThreadEvent, INFINITE);
        printf("%s 等到事件被触发,顺利结束
    ", (PSTR)pM);
        return 0;
    }
    
    void fun2()
    {
        bool bManualReset = true;
        ThreadEvent = CreateEvent(NULL, bManualReset, false, NULL);
        if (bManualReset)
        {
            printf("当前使用手动置位事件
    ");
        }
        else
        {
            printf("当前使用自动置位事件
    ");
        }
    
        char szFast[5][30]{ "快线程001","快线程002","快线程003","快线程004","快线程005" };
        char szSlow[5][30]{ "慢线程001","慢线程002","慢线程003","慢线程004","慢线程005" };
    
        size_t i = 0;
        for ( i = 0; i < 5; i++)
        {
            _beginthreadex(NULL, 0, FastThreadFun, szFast[i], 0, NULL);
        }
        for (i = 0; i < 5; i++)
        {
            _beginthreadex(NULL, 0, SlowThreadFun, szSlow[i], 0, NULL);
        }
    
        Sleep(50); //确保快线程已经全部启动
        printf("现在主线程触发事件脉冲-PlusEvent
    ");
        PulseEvent(ThreadEvent); //调用PulseEvent()就相当于同时调用下面二句 
        //SetEvent(ThreadEvent);
        //ResetEvent(ThreadEvent);
    
        Sleep(3000);
        printf("时间到,主线程结束运行
    ");
        CloseHandle(ThreadEvent);
    }
    
    int main(void)
    {
        //test1
        //fun1();
    
        //test2
        fun2();
    
        //检测内存泄漏
        _CrtDumpMemoryLeaks();
        return 0;
    }

    fun1:成功解决临界资源的问题

    fun2:PulseEvent:

    函数说明:这是一个不常用的事件函数,此函数相当于SetEvent()后立即调用ResetEvent();此时情况可以分为两种:

    1.对于手动置位事件,所有正处于等待状态下线程都变成可调度状态。

    2.对于自动置位事件,所有正处于等待状态下线程只有一个变成可调度状态(随机)。

     

  • 相关阅读:
    WCF里几个基本知识点
    MVC3+EntityFramework实践笔记
    一些vim的插件和配置
    Web API工作方式
    计算机中的异常
    Glusterfs之nfs模块源码分析
    ASP.NET Windows身份认证
    Sql Server表结构及索引辅助查看工具
    sql server批量插入与更新两种解决方案
    如何在ViewModel中正确地使用Timer(定时器)
  • 原文地址:https://www.cnblogs.com/jason1990/p/4716428.html
Copyright © 2020-2023  润新知