• Win32线程安全问题.同步函数


     

                  线程安全问题.同步函数

    一丶简介什么是线程安全

      通过上面几讲.我们知道了线程怎么创建.线程切换的原理(CONTEXT结构) 每个线程在切换的时候都有自己的堆栈.

    但是这样会有安全问题. 为什么?  我们每个线程都使用自己的局部变量这个是没有安全问题的. 但是线程可能会使用全局变量.这样很有可能会产生安全问题.为什么是很有可能.

    1.有全局变量的情况下.有可能会有安全问题.

    2.对全局变量进行写操作.则一定有安全问题. 

    上面两个条件都具备,线程才是不安全的.

    为什么是不安全的.

    试想一下. 如果这个全局变量在更改.另一个线程也更改了.最后则会出现两个线程同时更改这个全局变量. 问题就会出现在这.

    例如以下代码:

    // 临界区同步函数.cpp : 定义控制台应用程序的入口点。
    //
    
    #include "stdafx.h"
    #include <Windows.h>
    DWORD g_Number = 10;
    DWORD WINAPI MyThreadFun1(LPVOID lParame)
    {
        while (g_Number > 0)
        {
            printf("+++剩下Number个数 = %d
    ", g_Number);
            g_Number--;
            printf("+++当前的Number个数 = %d
    ", g_Number);
        }
        return 0;
    }
    
    DWORD WINAPI MyThreadFun2(LPVOID lParame)
    {
        while (g_Number > 0 )
        {
            printf("***剩下Number个数 = %d
    ", g_Number);
            g_Number--;                                           //产生线程安全问题
            printf("***当前的Number个数 = %d
    ", g_Number);
        }
        return 0;
    }
    
    int main(int argc,char *argv[])
    {
        HANDLE hThreadHand[2] = { NULL };
        hThreadHand[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyThreadFun1, NULL, 0, NULL);
        hThreadHand[1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyThreadFun2, NULL, 0, NULL); //创建两个线程
        WaitForMultipleObjects(2, hThreadHand, TRUE, INFINITE);
        
    
    
        printf("Number个数 = %d 
    ", g_Number);
        system("pause");
        return 0;
    }

    上面的代码很简单. 看下运行结果

    为什么会产生这个问题.原因是.在线程中我们有个地方

    while(全局变量 > 0) 则会执行下边代码. 但是很有可能执行完这一句. 线程发生了切换. 去执行另一个线程去了. 最终会产生这样的结果.

    如果看反汇编.则会发现 全局变量--的地方.汇编代码 并不是一局. 如果发生线程切换则会出现错误.

    首先获取全局变量的值.

    然后sub -1

    最后重新赋值.

    很有可能在sun eax 1的时候就发生了切换. 这样就有安全问题了.为了解决这些问题.我们必须想办法. 所以Windows提供了一组线程同步的函数.

    二丶线程同步函数之临界区

    什么时候临界区. 临界区的意思就是 这一个区域我给你锁定.当前有且只能有一个线程来执行我们临界区的代码.

    而临界资源是一个全局变量

    临界区的使用步骤.

    1.创建全局原子变量. 

    2.初始化全原子变量

    3.进入临界区

    4.释放临界区.

    5.删除临界区.

    具体API:

      1.全局原子变量

    CRITICAL_SECTION g_cs;  //直接创建即可.不用关心内部实现.

      2.初始化全局原子变量.InitializeCriticalSection

        _Maybe_raises_SEH_exception_ VOID InitializeCriticalSection(
            LPCRITICAL_SECTION lpCriticalSection                       //传入全局原子变量的地址
        );

          3.使用的API 进入临界区.

    void EnterCriticalSection(
      LPCRITICAL_SECTION lpCriticalSection      //全局原子变量
    );

    下面还有一个. 是尝试无阻塞模式进入临界区. 意思就是内部加了一个判断.是否死锁了.

    BOOL TryEnterCriticalSection(                  返回吃持有的临界区对象.如果成功的情况下.
      LPCRITICAL_SECTION lpCriticalSection
    );

      4.使用API 释放临界区.

      

    void LeaveCriticalSection(
      LPCRITICAL_SECTION lpCriticalSection         //全局原子对象
    );

      5.删除临界区对象.

    void DeleteCriticalSection(
      LPCRITICAL_SECTION lpCriticalSection
    );

    代码例子:

    // 临界区同步函数.cpp : 定义控制台应用程序的入口点。
    //
    
    #include "stdafx.h"
    #include <Windows.h>
    //创建临界区结构
    CRITICAL_SECTION g_cs;
    
    DWORD g_Number = 10;
    DWORD WINAPI MyThreadFun1(LPVOID lParame)
    {
        EnterCriticalSection(&g_cs); //进入临界区
        while (g_Number > 0)
        {
            printf("+++剩下Number个数 = %d
    ", g_Number);
            g_Number--;
            printf("+++当前的Number个数 = %d
    ", g_Number);
        }
        LeaveCriticalSection(&g_cs);
        return 0;
    }
    
    DWORD WINAPI MyThreadFun2(LPVOID lParame)
    {
        EnterCriticalSection(&g_cs); //进入临界区
        while (g_Number > 0 )
        {
            printf("***剩下Number个数 = %d
    ", g_Number);
            g_Number--;                                                                 //while语句内就是临界区了.有且只能一个线程访问.
            printf("***当前的Number个数 = %d
    ", g_Number);
        }
        LeaveCriticalSection(&g_cs);
        return 0;
    }
    
    int main(int argc,char *argv[])
    {
        //初始化临界区全局原子变量
        InitializeCriticalSectionAndSpinCount(&g_cs, 0x00000400);
        //InitializeCriticalSection(&g_cs);                      //初始化临界区.两个API都可以.
    
        HANDLE hThreadHand[2] = { NULL };
        hThreadHand[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyThreadFun1, NULL, 0, NULL);
        hThreadHand[1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyThreadFun2, NULL, 0, NULL); //创建两个线程
        WaitForMultipleObjects(2, hThreadHand, TRUE, INFINITE);
        
        DeleteCriticalSection(&g_cs); //删除临界区.
    
        printf("+Number个数 = %d 
    ", g_Number);
        system("pause");
        return 0;
    }

    官方MSDN例子:

    链接:  https://docs.microsoft.com/zh-cn/windows/desktop/Sync/using-critical-section-objects

    三丶线程同步之互斥体

    1.临界区缺点.以及衍生出来的跨进程保护

     上面讲了临界区. 但是我们的临界资源是一个全局变量.例如下图:

    如果我们的临界资源是一个文件. 需要两个进程都要访问怎么办? 此时临界区已经不可以跨进程使用了.

    2.跨进程控制.

      跨进程控制就是指 不同进程中的多线程控制安全..比如A进程访问临界资源的时候. B进程不能访问. 因为临界区的 令牌.也就是我们说的全局原子变量.只能在应用层.

    但是如果放到内核中就好办了. 如下图所示

      

    A进程的线程从内核中获取互斥体. 为0 还是为1. B进程一样. 如果为 0 则可以进行访问临界资源.  访问的时候.互斥体则设置为1(也就是令牌设置为1)这样B进程就获取不到了.自然不能访问

    临界区资源了.

    3.互斥体操作API

      既然明白了互斥体是一个内核层的原子操作.那么我们就可以使用API 进行操作了.

    操作步骤.

        1.创建互斥体. 信号量设置为有信号的状态    例如全局的原子变量现在是有信号.是可以进行访问的.

        2.获取信号状态. 如果有信号则进入互斥体临界区执行代码.此时互斥体信号为无信号. 也就是说别的进程访问的时候.因为没有信号.执行不了代码.

        3.释放互斥体. 信号状态为有信号. 此时别的进程信号已经有了.所以可以进行访问了.

    具体API:

    1.创建互斥体

    HANDLE CreateMutexA(
      LPSECURITY_ATTRIBUTES lpMutexAttributes,          SD安全属性.句柄是否可以继承.每个内核对象API都拥有.
      BOOL                  bInitialOwner,              初始的信号量状态. false为有信号. 获取令牌的时候可以获取到. True为无信号. 且如果为True互斥体对象为线程拥有者.
      LPCSTR                lpName                      全局名字. 根据名字寻找互斥体. 
    );

    2.获取令牌.

      

    DWORD WaitForSingleObject(
      HANDLE hHandle,                          等待的内核对象
      DWORD  dwMilliseconds                 等待的时间
    );

    调用此函数之后.信号为无信号.别的进程是进入不了互斥体临界区的.

    3.释放互斥体

       

    BOOL ReleaseMutex(
      HANDLE hMutex
    );

    调用完比之后.互斥体为有信号.可以使用了.

     代码例子:

      两个工程代码是一样的.贴一份出来.

    #include "stdafx.h"
    #include <Windows.h>
    //创建临界区结构
    
    int main(int argc,char *argv[])
    {
        //初始化临界区全局原子变量
        HANDLE MutexHandle = CreateMutex(NULL, FALSE, TEXT("AAA"));  //创建互斥体. 信号量为0. 有信号的状态.wait可以等待
    
        WaitForSingleObject(MutexHandle,INFINITE);
        
    
        for (size_t i = 0; i < 10; i++)
        {
            Sleep(1000);
            printf("A进程访问临街资源中临街资源ID = %d 
    ", i);
        }
    
        ReleaseMutex(MutexHandle);
        return 0;
    }

    先运行A进程在运行B进程. 则B进程处于卡死状态.

    实现了同步. 除非A进程释放互斥体句柄使信号变为有信号.此时才可以访问B

    官方代码例子:

      

    #include <windows.h>
    #include <stdio.h>
    
    #define THREADCOUNT 2
    
    HANDLE ghMutex; 
    
    DWORD WINAPI WriteToDatabase( LPVOID );
    
    int main( void )
    {
        HANDLE aThread[THREADCOUNT];
        DWORD ThreadID;
        int i;
    
        // Create a mutex with no initial owner
    
        ghMutex = CreateMutex( 
            NULL,              // default security attributes
            FALSE,             // initially not owned               有信号
            NULL);             // unnamed mutex                     不需要跨进程使用.所以不用名字
    
        if (ghMutex == NULL) 
        {
            printf("CreateMutex error: %d
    ", GetLastError());
            return 1;
        }
    
        // Create worker threads
    
        for( i=0; i < THREADCOUNT; i++ )
        {
            aThread[i] = CreateThread(                                       //创建  THREADCOUNT个线程
                         NULL,       // default security attributes
                         0,          // default stack size
                         (LPTHREAD_START_ROUTINE) WriteToDatabase, 
                         NULL,       // no thread function arguments
                         0,          // default creation flags
                         &ThreadID); // receive thread identifier
    
            if( aThread[i] == NULL )
            {
                printf("CreateThread error: %d
    ", GetLastError());
                return 1;
            }
        }
    
        // Wait for all threads to terminate
    
        WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);                   //等待线程执行完毕
    
        // Close thread and mutex handles
    
        for( i=0; i < THREADCOUNT; i++ )
            CloseHandle(aThread[i]);
    
        CloseHandle(ghMutex);
    
        return 0;
    }
    
    DWORD WINAPI WriteToDatabase( LPVOID lpParam )
    { 
        // lpParam not used in this example
        UNREFERENCED_PARAMETER(lpParam);
    
        DWORD dwCount=0, dwWaitResult; 
    
        // Request ownership of mutex.
    
        while( dwCount < 20 )
        { 
            dwWaitResult = WaitForSingleObject(                                //线程内部等待互斥体.因为一开始为FALSE所以有信号.第一次执行线程的时候则会执行. 
                ghMutex,    // handle to mutex
                INFINITE);  // no time-out interval
     
            switch (dwWaitResult) 
            {
                // The thread got ownership of the mutex
                case WAIT_OBJECT_0: 
                    __try { 
                        // TODO: Write to the database
                        printf("Thread %d writing to database...
    ", 
                                GetCurrentThreadId());
                        dwCount++;
                    } 
    
                    __finally { 
                        // Release ownership of the mutex object
                        if (! ReleaseMutex(ghMutex))                                         //执行完毕.释放互斥体.信号量变为有信号. 其余线程等待的时候可以等到则可以继续执行线程代码
                        { 
                            // Handle error.
                        } 
                    } 
                    break; 
    
                // The thread got ownership of an abandoned mutex
                // The database is in an indeterminate state
                case WAIT_ABANDONED: 
                    return FALSE; 
            }
        }
        return TRUE; 
    }

     四丶事件操作API

      相应的管理线程同步操作的.还有事件. 

    具体API:

      1.创建事件对象

    HANDLE CreateEventA(
      LPSECURITY_ATTRIBUTES lpEventAttributes,   SD安全属性
      BOOL                  bManualReset,                通知类型
      BOOL                  bInitialState,                 初始值有信号还是无信号.false无信号
      LPCSTR                lpName                          全局名字
    );        
                                                                       返回事件句柄

    首先这个函数有点复杂. 主要是第二个跟第三个参数.

    第三个参数我们很好理解. 有信号还是无信号.  false为无信号. true为有信号.  这样Wait函数根据有无信号就可以进行线程是否执行了.

    主要是第二个参数. 通知类型.这个比较复杂.

    通知类型的意思就是指.  如果我们按照以前.我们使用了wait函数. 那么有信号会变为无信号.除非释放才会继续有信号执行.

    而现在的通知类型如果为TRUE. 那么wait函数执行的时候.你有信号我不会自动变为无信号了.除非你手动自己更改.

    如果通知类型为FALSE 那么则自动设置.有信号使用wait函数接受了.那么就变成无信号了.

    2.设置信号状态

    上面说了.如果为TRUE. 那么信号就不会自动设置了.那么需要我们手动设置.

    具体API

      设置为有信号状态

    BOOL SetEvent(
      HANDLE hEvent
    );

    那么相应的也有设置为无信号的状态

    BOOL ResetEvent(
      HANDLE hEvent
    );

    3.具体代码例子

    初始值为有信号状态. 通知类型为TRUE的情况下.

    #include "stdafx.h"
    #include <Windows.h>
    HANDLE g_hEvent;
    DWORD WINAPI MyThreadFun1(LPVOID lparam)
    {
        WaitForSingleObject(g_hEvent, INFINITE);// 等待事件对象
        for (size_t i = 0; i < 2; i++)
        {
            printf("A线程执行EIP = %d
    ", i);
        }
        return 0;
    }
    
    DWORD WINAPI MyThreadFun2(LPVOID lparam)
    {
        WaitForSingleObject(g_hEvent, INFINITE);// 等待事件对象
    
        for (size_t i = 0; i < 2; i++)
        {
            printf("B线程执行EIP = %d
    ", i);
        }
        return 0;
    }
    
    int main(int argc, char *argv[])
    {
        
        
        g_hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);   //通知类型为TRUE,则wait函数不手动将信号设置为无信号.
        
        HANDLE hThread[2] = { NULL };
        hThread[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyThreadFun1, 0, 0, NULL);
        hThread[1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyThreadFun2, 0, 0, NULL);
    
        WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
    
        CloseHandle(hThread[0]);
        CloseHandle(hThread[1]);
        CloseHandle(g_hEvent);
    
        system("pause");
        return 0;
    }

    根据上面代码我们设置的通知类型为TRUE. 那么则AB线程都会执行. 因为wait函数不会将信号设置为无信号了.

    结果演示.

    但是如果我们设置为信号状态为无信号的情况下.

    也就是将上面的创建事件的代码. 的第三个参数设置为FALSE

    启动后线程都会被阻塞.

    那么如果我们设置通知类型为FALSE .且信号类型是有信号的情况下.

    看看其执行结果

    可以看到只会执行线程A. 因为通知类型我们改为FALSE. 那么wait函数则会自动设置信号状态了. 也就是说我们创建的事件一开始是有信号的.

    首先执行线程A. 线程A 里面的wait函数等待到了有信号. 那么就会执行代码了. 此时信号状态已经设置为无信号了. 所以线程B就会阻塞到wait函数哪里.

    如果此时我们想执行完A之后再执行B. 那么可以使用 SetEvent设置信号为有信号.

    如果我们用于编程的话.大部分通知类型会改成FALSE. 让它自动设置信号状态为无信号. 我们可以使用API SetEvent设置有信号.

    这样编程比较简单. 如果设置通知类型为TRUE. 那么我们就要使用 ResetEvent 跟SetEvent配合了.

    如以下图片所示代码:

    g_hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);  不截图了.通知类型改为TRUE. 手动设置通知类型.

     五丶 预留位.信号量的预留

    六丶互斥本质跟同步本质

    什么是互斥.什么是同步. 上面我们说了很多线程同步函数.那么是否是真的同步了.不见得. 了解了互斥跟同步的本质.才能更好的编写同步代码.

    1.什么是互斥.什么是同步?

      互斥: 互斥就是指一块资源.当前访问的时候有且只有一个线程访问. 比如A访问的时候 B会阻塞.访问不了.

      同步: 同步的意思就是让线程执行顺序是有序的.因为互斥可以保证A访问的时候B访问不了.但有可能A会访问多次.线程无序.此时同步的意思就是 我就想让A执行完在执行B.

    这个就是同步.

    经典互斥例子.可以以这个例子讲解同步跟互斥.

    如下代码:

      

    #include "stdafx.h"
    #include <Windows.h>
    HANDLE g_hMutex;
    DWORD g_Money;
    DWORD g_MAX = 10;
    DWORD WINAPI MyThreadFun1(LPVOID lparam)
    {
        WaitForSingleObject(g_hMutex, INFINITE);// 等待事件对象
        for (size_t i = 0; i < g_MAX; i++)
        {
            g_Money = 1;  //消费者修改金钱
            DWORD dwTid = GetCurrentThreadId();
            printf("线程%d 生产者执行收钱动作.当前金钱 = %d
    ", dwTid,g_Money);
            ReleaseMutex(g_hMutex);
        }
    
        return 0;
    }
    
    DWORD WINAPI MyThreadFun2(LPVOID lparam)
    {
        WaitForSingleObject(g_hMutex, INFINITE);// 等待事件对象
        //因为通知类型为TRUE.所以我们必须手动设置信号状态为无信号
        for (size_t i = 0; i < g_MAX; i++)
        {
            g_Money = 0;  //消费者修改金钱
            DWORD dwTid = GetCurrentThreadId();
            printf("线程%d 消费者执行花钱动作.当前金钱 = %d
    ",dwTid, g_Money);
            ReleaseMutex(g_hMutex);
    
        }
        return 0;
    }
    
    int main(int argc, char *argv[])
    {
        
        
        g_hMutex = CreateMutex(NULL, FALSE,NULL);     //创建互斥体.信号为有信号
        
        HANDLE hThread[2] = { NULL };
        hThread[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyThreadFun1, 0, 0, NULL);
        hThread[1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyThreadFun2, 0, 0, NULL);
    
        WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
    
        CloseHandle(hThread[0]);
        CloseHandle(hThread[1]);
        CloseHandle(g_hMutex);
    
        system("pause");
        return 0;
    }

    一个线程修改为1.一个线程修改为0.那么按照正常逻辑.应该是生产一个.消费一个 .

    观看结果.结果是迥然不同的.

    并不是生产一个.释放一个.

    那如何变成 生产一个消费一个的这种模式那. 那么我们可以写一个简单的例子.判断我们的金钱来进行是否修改.

    修改代码为如下代码.

    #include "stdafx.h"
    #include <Windows.h>
    HANDLE g_hMutex;
    DWORD g_Money = 0;
    DWORD g_MAX = 10;
    DWORD g_IsChange;
    DWORD WINAPI MyThreadFun1(LPVOID lparam)
    {
        
        for (size_t i = 0; i < g_MAX; i++)
        {
            WaitForSingleObject(g_hMutex, INFINITE);// 等待事件对象
            if (g_Money == 0)
            {
                g_Money = 1;  //消费者修改金钱
                DWORD dwTid = GetCurrentThreadId();
                printf("线程%d 生产者执行收钱动作.当前金钱 = %d
    ", dwTid, g_Money);
                
            }
            else
            {
                i--; //因为如果不想等.循环次数会浪费.所以-1次.不让它浪费.
            }
            ReleaseMutex(g_hMutex);
        }
            
    
        return 0;
    }
    
    DWORD WINAPI MyThreadFun2(LPVOID lparam)
    {
    
        //因为通知类型为TRUE.所以我们必须手动设置信号状态为无信号
        for (size_t i = 0; i < g_MAX; i++)
        {
            WaitForSingleObject(g_hMutex, INFINITE);// 等待信号
            if (g_Money == 1)
            {
                g_Money = 0;  //消费者修改金钱
                DWORD dwTid = GetCurrentThreadId();
                printf("线程%d 消费者执行花钱动作.当前金钱 = %d
    ", dwTid, g_Money);
            }
            else
            {
                i--;
            }
            
            ReleaseMutex(g_hMutex);
    
        }
        return 0;
    }
    
    int main(int argc, char *argv[])
    {
        
        
        g_hMutex = CreateMutex(NULL, FALSE,NULL);
        
        HANDLE hThread[2] = { NULL };
        hThread[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyThreadFun1, 0, 0, NULL);
        hThread[1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyThreadFun2, 0, 0, NULL);
    
        WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
    
        CloseHandle(hThread[0]);
        CloseHandle(hThread[1]);
        CloseHandle(g_hMutex);
    
        system("pause");
        return 0;
    }

    代码结果演示

    发现已经实现了同步.但是这样写是有问题的. 浪费了大量的时间. 

    因为当线程执行的时候.如果判断不是1则会继续循环.而没有释放信号. 而我们要实现的则是.如果没有.则给下一个线程继续执行.且保证有序.

    所以上面的代码虽然实现了但是还是不能保证同步.会浪费线程的时间.

    如果要实现同步.那么只能用事件来实现了. 所以说 同步函数各有优缺点.

    实现同步的方法.

    1.创建两个Event对象.一个有信号.一个无信号.且通知类型都是自动设置的.也就是参数2为FALSE.

    2.当A线程执行完毕之后.使用SetEvent给B线程设置信号状态为有信号.这样B就会执行. B执行完之后给A设置.这样A就执行.相当于交错设置.不浪费时间片.

    如下代码演示:

    #include "stdafx.h"
    #include <Windows.h>
    HANDLE g_hEventOne;
    HANDLE g_hEventTwo;
    
    DWORD g_Money = 0;
    DWORD g_MAX = 10;
    DWORD g_IsChange;
    DWORD WINAPI MyThreadFun1(LPVOID lparam)
    {
        
        for (size_t i = 0; i < g_MAX; i++)
        {
            WaitForSingleObject(g_hEventOne, INFINITE);// 等待事件对象
    
            g_Money = 1;  //消费者修改金钱
            DWORD dwTid = GetCurrentThreadId();
            printf("线程%d 生产者执行收钱动作.当前金钱 = %d
    ", dwTid, g_Money);
                
            SetEvent(g_hEventTwo);
            
        }
            
    
        return 0;
    }
    
    DWORD WINAPI MyThreadFun2(LPVOID lparam)
    {
    
        //因为通知类型为TRUE.所以我们必须手动设置信号状态为无信号
        for (size_t i = 0; i < g_MAX; i++)
        {
            WaitForSingleObject(g_hEventTwo, INFINITE);// 等待事件对象
    
            g_Money = 0;  //消费者修改金钱
            DWORD dwTid = GetCurrentThreadId();
            printf("线程%d 消费者执行消费动作.当前金钱 = %d
    ", dwTid, g_Money);
    
            SetEvent(g_hEventOne);
    
        }
    
        return 0;
    }
    
    int main(int argc, char *argv[])
    {
        
        
        g_hEventOne = CreateEvent(NULL, FALSE, TRUE, NULL);  //A线程设置为有信号则A线程先执行.
        g_hEventTwo = CreateEvent(NULL, FALSE, FALSE, NULL);//B线程设置为无信号.则B线程不会先执行.要等A线程通知才可以执行.
    
        HANDLE hThread[2] = { NULL };
        hThread[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyThreadFun1, 0, 0, NULL);
        hThread[1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyThreadFun2, 0, 0, NULL);
    
        WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
    
        CloseHandle(hThread[0]);
        CloseHandle(hThread[1]);
        CloseHandle(g_hEventOne);
        CloseHandle(g_hEventTwo);
    
        system("pause");
        return 0;
    }

    代码执行结果

    实现了同步有序

  • 相关阅读:
    正则表达式  语法
    正则表达式  语法
    SQL Server 删除日志文件
    SQL Server 删除日志文件
    C# CLR简介
    C# CLR简介
    理解 C# 项目 csproj 文件格式的本质和编译流程
    理解 C# 项目 csproj 文件格式的本质和编译流程
    De4Dot+Reflector 支持多种反混淆
    De4Dot+Reflector 支持多种反混淆
  • 原文地址:https://www.cnblogs.com/iBinary/p/9574211.html
Copyright © 2020-2023  润新知