zoukankan      html  css  js  c++  java
  • 多线程编程示例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.对于自动置位事件,所有正处于等待状态下线程只有一个变成可调度状态(随机)。

     

  • 相关阅读:
    convert image to base64 and post to RESTful wcf
    在android webview实现截屏的手动tounchmove裁剪图片
    How to use jquery ajax and android request security RESTful WCF
    using swfUpload in asp.net mvc
    using HttpClient and sending json data to RESTful server in adroind
    ODP.NET数据访问
    android image watermark
    解决国内不能访问github的问题
    idapro权威指南第二版阅读笔记第九章 交叉引用和绘图功能
    idapro权威指南第二版阅读笔记第二章 逆向和反汇编工具
  • 原文地址:https://www.cnblogs.com/jason1990/p/4716428.html
Copyright © 2011-2022 走看看