• VC++ 多线程同步


    引出问题

    两个线程同时吃苹果的问题

    // Thread.cpp : 定义控制台应用程序的入口点。
    //
    #include "stdafx.h"
    #include "Windows.h"
    
    //苹果的总数
    int total = 50;
    
    DWORD WINAPI ThreadProc1(LPVOID lpParameter)
    {
        for (int i = 0; i < 50; ++i)
        {
            if (total > 0)
            {
                printf("线程1吃了编号为%d的苹果
    ", total);
                total--;
                Sleep(2);
            }
        }
        return 0;
    }
    DWORD WINAPI ThreadProc2(LPVOID lpParameter)
    {
        for (int i = 0; i < 50; ++i)
        {
            if (total > 0)
            {
                printf("线程2吃了编号为%d的苹果
    ", total--);
                Sleep(2);
            }
        }
        return 0;
    }
    int _tmain(int argc, _TCHAR* argv[])
    {
        HANDLE hThread1 = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
        HANDLE hThread2 = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
        CloseHandle(hThread1);
        CloseHandle(hThread2);
        Sleep(4000);//主线程睡眠4秒钟,放弃执行的权利,给其它线程运行的时间。因为一旦主线程退出,则进程退出,其它线程也将退出
        return 0;
    }
    View Code

    结果:

     明显,两线程需要同步。

    同步线程的方法

    1. 临界区(CriticalSection),又称关键代码段

    通常把多线程中访问同一个资源的代码段作为关键代码段。
    需要四个函数(所在头文件  <winbase.h> 或者 windows.h):

    void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection ); //初始化临界区
    void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection );//释放资源
    //--------------------------------------------------------------
    void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection ); //进入CriticalSection
    void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection ); //离开CriticalSection

    例子

    // Thread.cpp : 定义控制台应用程序的入口点。
    //
    
    #include "stdafx.h"
    #include "Windows.h"
    
    //苹果的总数
    int total = 50;
    //定义一个全局的CriticalSection
    CRITICAL_SECTION g_cs;
    
    DWORD WINAPI ThreadProc1(LPVOID lpParameter)
    {
        for (int i = 0; i < 50; ++i)
        {
            EnterCriticalSection(&g_cs);//进入
            if (total > 0)
            {
                printf("线程1吃了编号为%d的苹果
    ", total);
                total--;
                Sleep(2);
            }
            LeaveCriticalSection(&g_cs);//离开
        }
        return 0;
    }
    DWORD WINAPI ThreadProc2(LPVOID lpParameter)
    {
        for (int i = 0; i < 50; ++i)
        {
            EnterCriticalSection(&g_cs);//进入
            if (total > 0)
            {
                printf("线程2吃了编号为%d的苹果
    ", total--);
                Sleep(2);
            }
            LeaveCriticalSection(&g_cs);//离开
        }
        return 0;
    }
    int _tmain(int argc, _TCHAR* argv[])
    {
        //初始化临界区
        ::InitializeCriticalSection(&g_cs);
    
        HANDLE hThread1 = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
        HANDLE hThread2 = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
        CloseHandle(hThread1);
        CloseHandle(hThread2);
        Sleep(4000);//主线程睡眠4秒钟,放弃执行的权利,给其它线程运行的时间。因为一旦主线程退出,则进程退出,其它线程也将退出
    
        //释放临界区
        ::DeleteCriticalSection(&g_cs);
    
        return 0;
    }
    View Code

    封装一个AutoLock类

    class CAutoLock
    {
    private:
        CRITICAL_SECTION  m_cs;
    public:
        CAutoLock()
        {
            InitializeCriticalSection(&m_cs);
            Lock();//如果是用的时候只定义锁对象,可以不手动进入临界区和退出临界区
        }
        ~CAutoLock()
        {
            UnLock();//如果是用的时候只定义锁对象,可以不手动进入临界区和退出临界区
            DeleteCriticalSection(&m_cs);
        }
        void Lock()
        {
            EnterCriticalSection(&m_cs);
        }
        void UnLock()
        {
            LeaveCriticalSection(&m_cs);
        }
    };
    View Code

    2. 互斥对象(Mutex)

     互斥对象属于内核对象,它能确保线程对单个资源的互斥访问权。
    需要的几个函数,在<windows.h>

    // 创建互斥对象
    HANDLE CreateMutex( 
        LPSECURITY_ATTRIBUTES lpMutexAttributes, //Must be NULL. 
        BOOL bInitialOwner, //TRUE表示创建的线程获得互斥对象的所有权,FALSE反之
        LPCTSTR lpName );// 互斥对象的名称,NULL表示是匿名互斥对象
    //返回值:成功返回创建的互斥对象的句柄(若是命名的并已经存在,则返回已存在的互斥对象句柄;此时GetLastError获得ERROR_ALREADY_EXISTS),失败返回NULL
    
    // 释放互斥对象的所有权。releases ownership of the specified mutex object.
    BOOL ReleaseMutex(HANDLE hMutex);
    
    // 等待信号
    DWORD WaitForSingleObject(
      HANDLE hHandle,        // handle to object
      DWORD dwMilliseconds   // time-out interval 单位是毫秒
    );
    //If dwMilliseconds is zero, the function tests the object's state and returns immediately. If dwMilliseconds is INFINITE, the function's time-out interval never elapses. 
    //返回值:WAIT_ABANDONED The specified object is a mutex object that was not released by the thread that owned the mutex object before the owning thread terminated. Ownership of the mutex object is granted to the calling thread, and the mutex is set to nonsignaled. 
    //WAIT_OBJECT_0 The state of the specified object is signaled. 
    //WAIT_TIMEOUT The time-out interval elapsed, and the object's state is nonsignaled. 

     例子

    // 互斥对象的使用
    //
    
    #include "stdafx.h"
    #include "Windows.h"
    
    //苹果的总数
    int total = 50;
    //定义一个互斥对象的句柄
    HANDLE hMutex;
    
    DWORD WINAPI ThreadProc1(LPVOID lpParameter)
    {
        for (int i = 0; i < 50; ++i)
        {
            WaitForSingleObject(hMutex, INFINITE);//等待信号
            if (total > 0)
            {
                printf("线程1吃了编号为%d的苹果
    ", total);
                total--;
                Sleep(2);
            }
            ReleaseMutex(hMutex);//释放互斥对象的所有权
        }
        return 0;
    }
    DWORD WINAPI ThreadProc2(LPVOID lpParameter)
    {
        for (int i = 0; i < 50; ++i)
        {
            WaitForSingleObject(hMutex, INFINITE);//等待信号
            if (total > 0)
            {
                printf("线程2吃了编号为%d的苹果
    ", total--);
                Sleep(2);
            }
            ReleaseMutex(hMutex);//释放互斥对象的所有权
        }
        return 0;
    }
    int _tmain(int argc, _TCHAR* argv[])
    {
        //创建互斥对象
        hMutex = CreateMutex(NULL, FALSE, NULL);//第二个参数是FALSE,主线程不拥有互斥对象的所有权,即有信号状态
        HANDLE hThread1 = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
        HANDLE hThread2 = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
        CloseHandle(hThread1);
        CloseHandle(hThread2);
    
        Sleep(4000);//主线程睡眠4秒钟,放弃执行的权利,给其它线程运行的时间。因为一旦主线程退出,则进程退出,其它线程也将退出
    
        return 0;
    }
    View Code

     3. 事件对象

    事件对象也属于内核对象,它包含以下三个成员:
    1. 使用计数
    2. 用于指明该事件是一个自动重置的事件还是一个人工重置的事件的布尔值
    3. 用于指明该事件处于已通知状态还是未通知状态的布尔值。
    事件对象有两种不同的类型:人工重置的事件对象和自动重置的事件对象。
    当人工重置的事件对象得到通知时,等待该事件对象的所有线程均变为可调度线程。
    当一个自动重置的事件对象得到通知时,等待该事件对象的线程中只有一个线程变为可调度线程。

    The CreateEvent function creates or opens a named or unnamed event object. 
    
    HANDLE CreateEvent(
      LPSECURITY_ATTRIBUTES lpEventAttributes, // SD
      BOOL bManualReset,                       // reset type
      BOOL bInitialState,                      // initial state
      LPCTSTR lpName                           // object name
    );
    
    
    The SetEvent function sets the specified event object to the signaled state. 
    
    BOOL SetEvent(
      HANDLE hEvent   // handle to event
    );
    
    The ResetEvent function sets the specified event object to the nonsignaled state. 
    
    BOOL ResetEvent(
      HANDLE hEvent   // handle to event
    );

    例子

    // 事件对象的使用
    //
    
    #include "stdafx.h"
    #include "Windows.h"
    
    //苹果的总数
    int total = 50;
    //定义一个事件对象的句柄
    HANDLE hEvent;
    
    DWORD WINAPI ThreadProc1(LPVOID lpParameter)
    {
        for (int i = 0; i < 50; ++i)
        {
            WaitForSingleObject(hEvent, INFINITE);//等待信号,获得事件对象后自动设置为无信号
            if (total > 0)
            {
                printf("线程1吃了编号为%d的苹果
    ", total);
                total--;
                Sleep(2);
            }
            ::SetEvent(hEvent);//设置为有信号状态
        }
        return 0;
    }
    DWORD WINAPI ThreadProc2(LPVOID lpParameter)
    {
        for (int i = 0; i < 50; ++i)
        {
            WaitForSingleObject(hEvent, INFINITE);//等待信号,获得事件对象后自动设置为无信号
            if (total > 0)
            {
                printf("线程2吃了编号为%d的苹果
    ", total--);
                Sleep(2);
            }
            ::SetEvent(hEvent);//设置为有信号状态
        }
        return 0;
    }
    int _tmain(int argc, _TCHAR* argv[])
    {
        //创建一个自动重置事件内核对象(当线程获得事件对象后,操作系统会自动将事件对象设置为无信号状态)
        hEvent = CreateEvent(NULL, FALSE, TRUE, NULL);//初始状态是有信号
        HANDLE hThread1 = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
        HANDLE hThread2 = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
        CloseHandle(hThread1);
        CloseHandle(hThread2);
    
        Sleep(4000);//主线程睡眠4秒钟,放弃执行的权利,给其它线程运行的时间。因为一旦主线程退出,则进程退出,其它线程也将退出
        
        //关闭事件对象的句柄
        CloseHandle(hEvent);
        return 0;
    }
    View Code

     保证只有一个应用程序的实例运行,使用 Event 或 Mutex 都可以。

    // 先定义一个全局句柄  HANDLE g_hEvent;
    //保证应用程序只有一个在运行
        g_hEvent = CreateEvent(NULL, FALSE, FALSE, L"MyApplication");
        if (g_hEvent)
        {
            if (ERROR_ALREADY_EXISTS == GetLastError())
            {
                AfxMessageBox(_T("该程序已启动!"));
                return FALSE;
            }
        }
    View Code

    互斥对象、事件对象与关键代码段的比较

    互斥对象和事件对象都属于内核对象,利用内核对象进行线程同步时,速度较慢,但利用互斥对象和事件对象这样的内核对象,可以在多个进程中的各个线程间进行同步。
    关键代码段工作在用户方式下,同步速度较快,但在使用关键代码段时,很容易进入死锁状态,因为在等待进入关键代码段时无法设定超时值。
    通常,在编写多线程程序并需要实现线程同步时,首选关键代码段,由于它的使用比较简单。

    **********************

    常记溪亭日暮,沉醉不知归路。兴尽晚回舟,误入藕花深处。争渡,争渡,惊起一滩鸥鹭。

    昨夜雨疏风骤,浓睡不消残酒。试问卷帘人,却道海棠依旧。知否?知否?应是绿肥红瘦。
  • 相关阅读:
    Jenkins+Tomcat+svn+maven自动化构建简单过程
    Eclipse常用的6个Debug技巧
    在linux服务器上发布web应用的完整过程
    【转】解决response.AddHeader("Content-Disposition", "attachment; fileName=" + fileName) 中文显示乱码
    springmvc缓存和mybatis缓存
    springmvc文件上传和下载
    博客园API
    整理一下CoreGraphic和Quartz2D的知识(二)
    整理一下CoreGraphic和Quartz2D的知识(一)
    CGPoint和CGSize以及CGRect的一些方法~
  • 原文地址:https://www.cnblogs.com/htj10/p/12432844.html
Copyright © 2020-2023  润新知