【c++多线程】互斥量概念、用法、死锁演示以及unique_lock

第5节 互斥量概念、用法、死锁演示及解决详解

  • (1) 互斥量(mutex)的基本概念

  • (2)互斥量的用法

    • (2.1)lock(), unlock()
    • (2.2) std::lock_guard类模板
  • (3) 死锁

    • (3.1) 死锁演示
    • (3.2) 死锁的一般解决方案
    • (3.3) std::lock()函数模板
    • (3.4) std::lock_guard的std::adopt_lock参数

5.0 线程

这样写因为是有问题的,没有保护线程。

#include <iostream>
#include <list>
#include <thread>

using namespace std;

class A
{
public:
    // 把收到的消息入到一个队列的线程
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100000; ++i)
        {
            cout << "inMsgRecvQueue()执行,插入一个元素  " << i << endl;
            this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
        }
    }

    // 把数据从消息队列中取出的线程
    void outMsgRecvQueue()
    {
        for (int i = 0; i < 100000; ++i)
        {
            if (!this->msgRecvQueue.empty())
            {
                // 消息不为空
                int command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
                this->msgRecvQueue.pop_front();           // 移除第一个元素,但不返回

                // 这里考虑处理数据
            }
            else
            {
                // 消息队列为空
                cout << "outMsgRuecvQueue()执行,但目前消息队列中为空" << i << endl;
            }
        }
        cout << "end" << endl;
    }

private:
    std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
};

int main(int argc, char *argv[])
{

    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();

    system("pause");

    return 0;
}

5.1 互斥量(mutex)的基本概念

保护共享数据,操作时,某个用代码把共享数据锁住,其他想操作共享数据的线程必须等待解锁(锁定住,操作,解锁)

5.1.1 互斥量(mutex)的基本概念

互斥量是个类对象,理解成一把锁,多个线程尝试用lock()成员函数来加锁这把锁头,
只有一个线程能锁定成功(成功的标志是lock()函数返回。
如果没锁成功,那么流程卡在lock()这里不断的尝试去锁这把锁。

互斥量使用要小心,保护数据不多不少,少了,没达到保护效果,多了,影响效率。

5.2 互斥量用法

5.2.1 lock(),unlock()

步骤:先lock(),操作共享数据,最后unlock():

lock()unlock()要成对使用,有lock必须要有unlock,每调用一次lock(),必然应该调用一次unlock()

不应该也不允许调用1次lock(),却调用2次unlock(),也不允许调用2次lock()却调用1次unlock(),这些非对称数量的调用都会导致程序不稳定甚至崩溃。

加锁最简单版本:

#include <iostream>
#include <list>
#include <thread>
#include <mutex>

using namespace std;

class A
{
public:
    // 把收到的消息入到一个队列的线程
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100; ++i)
        {
            cout << "inMsgRecvQueue()执行,插入一个元素  " << i << endl;
            my_mutex.lock();
            this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
            my_mutex.unlock();
        }
    }

    bool outMsgLULProc(int &command)
    {
        my_mutex.lock();
        if (!this->msgRecvQueue.empty())
        {
            // 消息不为空
            command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
            this->msgRecvQueue.pop_front();       // 移除第一个元素,但不返回
            my_mutex.unlock();                    // 每个分支都要有unlock
            return true;
        }
        my_mutex.unlock(); // 每个分支都要有unlock
        return false;
    }

    // 把数据从消息队列中取出的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        // 用while
        while (1)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
                // 进行数据处理
                // ....
            }

            //  当全部读取完之后退出
            if (command == 99)
                break;
        }
        cout << "end" << endl;
    }

private:
    std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
    std::mutex my_mutex;         // 创建一个互斥量
};

int main(int argc, char *argv[])
{

    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();

    system("pause");

    return 0;
}

5.2.2 std::lock_guard类模板

为了防止忘记unlock(),引入了一个叫std::lock_guard的类模板。

和智能指针相似(unique_ptr<>),忘记释放内存不要紧,智能指针自己会释放。

std::lock_guard类模板,直接取代lock()unlock(),也就是说,有了lock_guard之后,再不能使用lock()unlock()

这也不用手动unlock

#include <iostream>
#include <list>
#include <thread>
#include <mutex>

using namespace std;

class A
{
public:
    // 把收到的消息入到一个队列的线程
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100; ++i)
        {
            cout << "inMsgRecvQueue()执行,插入一个元素  " << i << endl;
            std::lock_guard<std::mutex> sbguard(my_mutex);
            this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
        }
    }

    bool outMsgLULProc(int &command)
    {
        std::lock_guard<std::mutex> sbguard(my_mutex);
        if (!this->msgRecvQueue.empty())
        {
            // 消息不为空
            command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
            this->msgRecvQueue.pop_front();       // 移除第一个元素,但不返回
            return true;
        }
        return false;
    }

    // 把数据从消息队列中取出的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        // 用while
        while (1)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
                // 进行数据处理
                // ....
            }

            //  当全部读取完之后退出
            if (command == 99)
                break;
        }
        cout << "end" << endl;
    }

private:
    std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
    std::mutex my_mutex;         // 创建一个互斥量
};

int main(int argc, char const *argv[])
{

    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();

    system("pause");

    return 0;
}

使用大括号{}提前结束锁的周期

#include <iostream>
#include <list>
#include <thread>
#include <mutex>

using namespace std;

class A
{
public:
    // 把收到的消息入到一个队列的线程
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100; ++i)
        {
            cout << "inMsgRecvQueue()执行,插入一个元素  " << i << endl;
            {
                std::lock_guard<std::mutex> sbguard(my_mutex);
                this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
            }
            // 其他非共享代码
            // .....
        }
    }

    bool outMsgLULProc(int &command)
    {
        std::lock_guard<std::mutex> sbguard(my_mutex);
        if (!this->msgRecvQueue.empty())
        {
            // 消息不为空
            command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
            this->msgRecvQueue.pop_front();       // 移除第一个元素,但不返回
            return true;
        }
        return false;
    }

    // 把数据从消息队列中取出的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        // 用while
        while (1)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
                // 进行数据处理
                // ....
            }

            //  当全部读取完之后退出
            if (command == 99)
                break;
        }
        cout << "end" << endl;
    }

private:
    std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
    std::mutex my_mutex;         // 创建一个互斥量
};

int main(int argc, char const *argv[])
{

    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();

    system("pause");

    return 0;
}

5.3 死锁

死锁问题的产生至少需要两个互斥量才能产生。

比如:

两个互斥量,mutex1和mutex2

两个线程,A和B

  • 线程A执行的时候,这个线程先锁mutex1再锁mutex2
  • 出现上下文切换
  • 线程B执行了,这个线程先锁mutex2,因为mutex2还没有被锁,所以mutex2会锁成功,线程B要去锁mutex1了。。。
  • 这个就出现了死锁
  • 线程A因为拿不到mutex2,流程走不下去
  • 线程B因为拿不到mutex1,流程走不下去

5.3.1 死锁演示

这样写就会出现死锁

#include <iostream>
#include <list>
#include <thread>
#include <mutex>

using namespace std;

class A
{
public:
    // 把收到的消息入到一个队列的线程
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100; ++i)
        {
            cout << "inMsgRecvQueue()执行,插入一个元素  " << i << endl;
            my_mutex1.lock();   // 实际工程这两个
            my_mutex2.lock();
            this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
            my_mutex2.unlock();
            my_mutex1.unlock();
        }
    }

    bool outMsgLULProc(int &command)
    {
        my_mutex2.lock();
        my_mutex1.lock();
        if (!this->msgRecvQueue.empty())
        {
            // 消息不为空
            command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
            this->msgRecvQueue.pop_front();       // 移除第一个元素,但不返回
            my_mutex1.unlock();
            my_mutex2.unlock();
            return true;
        }
        my_mutex1.unlock();
        my_mutex2.unlock();
        return false;
    }

    // 把数据从消息队列中取出的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        // 用while
        while (1)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
                // 进行数据处理
                // ....
            }

            //  当全部读取完之后退出
            if (command == 99)
                break;
        }
        cout << "end" << endl;
    }

private:
    std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
    std::mutex my_mutex;         // 创建一个互斥量
    std::mutex my_mutex1;        // 第一把锁
    std::mutex my_mutex2;        // 第二把锁
};

int main(int argc, char const *argv[])
{

    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();

    system("pause");

    return 0;
}

5.3.2 死锁的一般解决方案

只要线程里面锁的顺序一致就不会出现死锁

#include <iostream>
#include <list>
#include <thread>
#include <mutex>

using namespace std;

class A
{
public:
    // 把收到的消息入到一个队列的线程
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100; ++i)
        {
            cout << "inMsgRecvQueue()执行,插入一个元素  " << i << endl;
            my_mutex1.lock();   // 实际工程这两个
            my_mutex2.lock();
            this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
            my_mutex2.unlock();
            my_mutex1.unlock();
        }
    }

    bool outMsgLULProc(int &command)
    {
        my_mutex1.lock();
        my_mutex2.lock();
        if (!this->msgRecvQueue.empty())
        {
            // 消息不为空
            command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
            this->msgRecvQueue.pop_front();       // 移除第一个元素,但不返回
            my_mutex2.unlock();
            my_mutex1.unlock();
            return true;
        }
        my_mutex1.unlock();
        my_mutex2.unlock();
        return false;
    }

    // 把数据从消息队列中取出的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        // 用while
        while (1)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
                // 进行数据处理
                // ....
            }

            //  当全部读取完之后退出
            if (command == 99)
                break;
        }
        cout << "end" << endl;
    }

private:
    std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
    std::mutex my_mutex;         // 创建一个互斥量
    std::mutex my_mutex1;        // 第一把锁
    std::mutex my_mutex2;        // 第二把锁
};

int main(int argc, char const *argv[])
{

    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();

    system("pause");

    return 0;
}

或者使用std::lock_guard

#include <iostream>
#include <list>
#include <thread>
#include <mutex>

using namespace std;

class A
{
public:
    // 把收到的消息入到一个队列的线程
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100; ++i)
        {
            cout << "inMsgRecvQueue()执行,插入一个元素  " << i << endl;
            std::lock_guard<std::mutex> sbguard1(my_mutex1);
            std::lock_guard<std::mutex> sbguard2(my_mutex2);
            this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
        }
    }

    bool outMsgLULProc(int &command)
    {
        std::lock_guard<std::mutex> sbguard1(my_mutex1);
        std::lock_guard<std::mutex> sbguard2(my_mutex2);
        if (!this->msgRecvQueue.empty())
        {
            // 消息不为空
            command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
            this->msgRecvQueue.pop_front();       // 移除第一个元素,但不返回
            return true;
        }
        return false;
    }

    // 把数据从消息队列中取出的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        // 用while
        while (1)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
                // 进行数据处理
                // ....
            }

            //  当全部读取完之后退出
            if (command == 99)
                break;
        }
        cout << "end" << endl;
    }

private:
    std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
    std::mutex my_mutex;         // 创建一个互斥量
    std::mutex my_mutex1;        // 第一把锁
    std::mutex my_mutex2;        // 第二把锁
};

int main(int argc, char const *argv[])
{

    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();

    system("pause");

    return 0;
}

5.3.3 std::lock() 函数模板

能力:一次锁住两个或者两个以上的互斥量(至少两个,多了不限,一个不行)

它不存在这种因为在两个线程中,因为锁的顺序问题导致死锁的风险问题。

std::lock():如果互斥量中有一个没锁住,它就在那里等着,等所有互斥量都锁住,它才能往下走(返回)。

要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,另一个没锁成功,则它立即把已经锁住的解锁。

#include <iostream>
#include <list>
#include <thread>
#include <mutex>

using namespace std;

class A
{
public:
    // 把收到的消息入到一个队列的线程
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100; ++i)
        {
            cout << "inMsgRecvQueue()执行,插入一个元素  " << i << endl;
            std::lock(my_mutex1, my_mutex2); //相当于每个互斥量都调用了.lock()
            this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
            my_mutex2.unlock();              // 前面锁住了两个,所以后面都需要解锁
            my_mutex1.unlock();
        }
    }

    bool outMsgLULProc(int &command)
    {
        //相当于每个互斥量都调用了.lock()
        std::lock(my_mutex1, my_mutex2);
        if (!this->msgRecvQueue.empty())
        {
            // 消息不为空
            command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
            this->msgRecvQueue.pop_front();       // 移除第一个元素,但不返回
            my_mutex2.unlock();                   // 前面锁住了两个,所以后面都需要解锁
            my_mutex1.unlock();
            return true;
        }
        my_mutex2.unlock(); // 前面锁住了两个,所以后面都需要解锁
        my_mutex1.unlock();
        return false;
    }

    // 把数据从消息队列中取出的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        // 用while
        while (1)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
                // 进行数据处理
                // ....
            }

            //  当全部读取完之后退出
            if (command == 99)
                break;
        }
        cout << "end" << endl;
    }

private:
    std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
    std::mutex my_mutex;         // 创建一个互斥量
    std::mutex my_mutex1;        // 第一把锁
    std::mutex my_mutex2;        // 第二把锁
};

int main(int argc, char const *argv[])
{

    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();

    system("pause");

    return 0;
}

5.3.4 std::lock_guard的std::adopt_lock参数

使用lock_guard主要是为了来解决unlock的问题。

std::adopt_lock是个结构体对象,起一个标记作用,作用就是:表示这个互斥量已经lock(),不需要在std::lock_guard<std::mutex>里面对mutex对象进行再次lock()了。

#include <iostream>
#include <list>
#include <thread>
#include <mutex>

using namespace std;

class A
{
public:
    // 把收到的消息入到一个队列的线程
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100; ++i)
        {
            cout << "inMsgRecvQueue()执行,插入一个元素  " << i << endl;
            std::lock(my_mutex1, my_mutex2); // 相当于每个互斥量都调用了 .lock()
            std::lock_guard<std::mutex> sbguard1(my_mutex1, std::adopt_lock);
            std::lock_guard<std::mutex> sbguard2(my_mutex2, std::adopt_lock); //这样就不用unlock,因为析构的时候会自动unlock
            this->msgRecvQueue.push_back(i);                                  // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
        }
    }

    bool outMsgLULProc(int &command)
    {
        std::lock(my_mutex1, my_mutex2); // 相当于每个互斥量都调用了 .lock()
        std::lock_guard<std::mutex> sbguard1(my_mutex1, std::adopt_lock);
        std::lock_guard<std::mutex> sbguard2(my_mutex2, std::adopt_lock); //这样就不用unlock,因为析构的时候会自动unlock
        if (!this->msgRecvQueue.empty())
        {
            // 消息不为空
            command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
            this->msgRecvQueue.pop_front();       // 移除第一个元素,但不返回

            return true;
        }
        return false;
    }

    // 把数据从消息队列中取出的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        // 用while
        while (1)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
                // 进行数据处理
                // ....
            }

            //  当全部读取完之后退出
            if (command == 99)
                break;
        }
        cout << "end" << endl;
    }

private:
    std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
    std::mutex my_mutex;         // 创建一个互斥量
    std::mutex my_mutex1;        // 第一把锁
    std::mutex my_mutex2;        // 第二把锁
};

int main(int argc, char const *argv[])
{

    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();

    system("pause");

    return 0;
}

总结,std::lock():一次锁定多个互斥量,谨慎使用(建议一个一个锁)

第六节 unique_lock详解

  • unique_lock 取代lock_quard
  • unique_lock的第二个参数
    • std::adopt_lock
    • std::try_to_lock
    • std::defer_lock
  • unique_lock的成员函数
    • lock()
    • unlock()
    • try_lock()
    • release()
  • unique_lock所有权的传递

6.1 unique_lock 取代lock_quard

unique_lock是个类模板,工作中,一般lock_guard(推荐使用);lock_guard取代了mutexlock()unlock()

unique_locklock_guard灵活很多,效率上差一点,内存占用多一点。

6.2 unique_lock的第二个参数

std::lock_guard<std::mutex> sbguard1(my_mutex1, std::adopt_lock); // adopt_lock标记作用

6.2.1 std::adopt_lock

  • std::adopt_lock:表示这个互斥量已经被lock了(你必须要把互斥量提前lock了,否则会报异常

  • std::adopt_lock标记的效果就是“假设调用方 线程已经拥有了互斥的所有权(已经lock()成功了)”

  • 通知lock_guard不需要在构造函数中lock这个互斥量了;

  • unique_lock也可以带std::adopt_lock标记,含义相同,就是不希望在unique_lock()的构造函数中lock这个mutex

使用adopt_lock需要加锁

#include <iostream>
#include <list>
#include <thread>
#include <mutex>

using namespace std;

class A
{
public:
    // 把收到的消息入到一个队列的线程
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100; ++i)
        {
            cout << "inMsgRecvQueue()执行,插入一个元素  " << i << endl;
            my_mutex1.lock();
            std::unique_lock<std::mutex> sbguard1(my_mutex1, std::adopt_lock); // 使用adopt_lock之前mutex要加锁
            this->msgRecvQueue.push_back(i);                                   // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
            // 。。。
            // 其他处理代码
        }
    }

    bool outMsgLULProc(int &command)
    {
        my_mutex1.lock();
        std::unique_lock<std::mutex> sbguard1(my_mutex1, std::adopt_lock);
        if (!this->msgRecvQueue.empty())
        {
            // 消息不为空
            command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
            this->msgRecvQueue.pop_front();       // 移除第一个元素,但不返回

            return true;
        }
        return false;
    }

    // 把数据从消息队列中取出的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        // 用while
        while (1)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
                // 进行数据处理
                // ....
            }

            //  当全部读取完之后退出
            if (command == 99)
                break;
        }
        cout << "end" << endl;
    }

private:
    std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
    std::mutex my_mutex;         // 创建一个互斥量
    std::mutex my_mutex1;        // 第一把锁
    std::mutex my_mutex2;        // 第二把锁
};

int main(int argc, char const *argv[])
{

    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();

    system("pause");

    return 0;
}

出现的情况

线程2中加入延迟,如果这样写,一旦进入线程2,连带线程1也会进行等待,也一样卡住了

所以就需要使用到try_to_lock(下一节)

#include <iostream>
#include <list>
#include <thread>
#include <mutex>

using namespace std;

class A
{
public:
    // 把收到的消息入到一个队列的线程
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100; ++i)
        {
            cout << "inMsgRecvQueue()执行,插入一个元素  " << i << endl;
            my_mutex1.lock();
            std::unique_lock<std::mutex> sbguard1(my_mutex1, std::adopt_lock); // 使用adopt_lock之前mutex要加锁
            this->msgRecvQueue.push_back(i);                                   // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
            // 。。。
            // 其他处理代码
        }
    }

    bool outMsgLULProc(int &command)
    {
        my_mutex1.lock();
        std::unique_lock<std::mutex> sbguard1(my_mutex1, std::adopt_lock);

        std::chrono::milliseconds dura(2000);
        std::this_thread::sleep_for(dura); // 休息2s

        if (!this->msgRecvQueue.empty())
        {
            // 消息不为空
            command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
            this->msgRecvQueue.pop_front();       // 移除第一个元素,但不返回

            return true;
        }
        return false;
    }

    // 把数据从消息队列中取出的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        // 用while
        while (1)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
                // 进行数据处理
                // ....
            }

            //  当全部读取完之后退出
            if (command == 99)
                break;
        }
        cout << "end" << endl;
    }

private:
    std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
    std::mutex my_mutex;         // 创建一个互斥量
    std::mutex my_mutex1;        // 第一把锁
    std::mutex my_mutex2;        // 第二把锁
};

int main(int argc, char const *argv[])
{

    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();

    system("pause");

    return 0;
}

6.2.2 std::try_to_lock

尝试用mutex的lock()去锁定这个mutex,但如果没有锁定成功,也会立即返回,并不会阻塞在那里,用这个 try_to_lock的前提是自己不能先去lock。

#include <iostream>
#include <list>
#include <thread>
#include <mutex>

using namespace std;

class A
{
public:
    // 把收到的消息入到一个队列的线程
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100; ++i)
        {
            cout << "inMsgRecvQueue()执行,插入一个元素  " << i << endl;
            // my_mutex1.lock();
            std::unique_lock<std::mutex> sbguard1(my_mutex1, std::try_to_lock); // 使用adopt_lock之前mutex要加锁

            if (sbguard1.owns_lock())
            {
                this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
                // 。。。
                // 其他处理代码
            }
            else
            {
                cout << "inMsgRecvQueue()执行,没有拿到锁,只能干点别的事情" << i << endl;
            }
        }
    }

    bool outMsgLULProc(int &command)
    {
        // my_mutex1.lock();
        std::unique_lock<std::mutex> sbguard1(my_mutex1);

        std::chrono::milliseconds dura(1000);
        std::this_thread::sleep_for(dura); // 休息2s

        if (!this->msgRecvQueue.empty())
        {
            // 消息不为空
            command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
            this->msgRecvQueue.pop_front();       // 移除第一个元素,但不返回

            return true;
        }
        return false;
    }

    // 把数据从消息队列中取出的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        // 用while
        while (1)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
                // 进行数据处理
                // ....
            }

            //  当全部读取完之后退出
            if (command == 99)
                break;
        }
        cout << "end" << endl;
    }

private:
    std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
    std::mutex my_mutex;         // 创建一个互斥量
    std::mutex my_mutex1;        // 第一把锁
    std::mutex my_mutex2;        // 第二把锁
};

int main(int argc, char const *argv[])
{

    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();

    system("pause");

    return 0;
}

6.2.3 std::defer_lock

  • 用这个defer_lock的前提是 你不能自己先lock,否则会报异常。(和try_to_lock一样,不能自己lock,用adopt_lock需要自己提前加lock)

  • defer_lock的意思 就是 并没有给mutex加锁:初始化一个没有加锁的mutex,但是后面需要对unique_lock对象进行加锁

  • 我们借着defer_lock的话题,来介绍一些unique_lock的重要成员函数

用刚才有延迟的函数做一下修改,这样改过之后会在线程2那边有等待,也就是线程1会跟着线程2有等待,有阻塞。

#include <iostream>
#include <list>
#include <thread>
#include <mutex>

using namespace std;

class A
{
public:
    // 把收到的消息入到一个队列的线程
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100; ++i)
        {
            cout << "inMsgRecvQueue()执行,插入一个元素  " << i << endl;
            // my_mutex1.lock();
            std::unique_lock<std::mutex> sbguard1(my_mutex1, std::defer_lock); 
            sbguard1.lock();        // 这边是对sbguard加锁,不用关心解锁,会自己解锁

            // if (sbguard1.owns_lock())
//             {
                this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
                // 。。。
                // 其他处理代码
            // }
            // else
            // {
            //     cout << "inMsgRecvQueue()执行,没有拿到锁,只能干点别的事情" << i << endl;
            // }
        }
    }

    bool outMsgLULProc(int &command)
    {
        // my_mutex1.lock();
        std::unique_lock<std::mutex> sbguard1(my_mutex1);

        std::chrono::milliseconds dura(100);
        std::this_thread::sleep_for(dura); // 休息2s

        if (!this->msgRecvQueue.empty())
        {
            // 消息不为空
            command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
            this->msgRecvQueue.pop_front();       // 移除第一个元素,但不返回

            return true;
        }
        return false;
    }

    // 把数据从消息队列中取出的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        // 用while
        while (1)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
                // 进行数据处理
                // ....
            }

            //  当全部读取完之后退出
            if (command == 99)
                break;
        }
        cout << "end" << endl;
    }

private:
    std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
    std::mutex my_mutex;         // 创建一个互斥量
    std::mutex my_mutex1;        // 第一把锁
    std::mutex my_mutex2;        // 第二把锁
};

int main(int argc, char const *argv[])
{

    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();

    system("pause");

    return 0;
}

非阻塞,使用sbguard1.try_lock()

#include <iostream>
#include <list>
#include <thread>
#include <mutex>

using namespace std;

class A
{
public:
    // 把收到的消息入到一个队列的线程
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100; ++i)
        {
            cout << "inMsgRecvQueue()执行,插入一个元素  " << i << endl;
            // my_mutex1.lock();
            std::unique_lock<std::mutex> sbguard1(my_mutex1, std::defer_lock);
            // sbguard1.lock(); // 这边是对sbguard加锁,不用关心解锁,会自己解锁

            // 因为用了defer_lock,所以这边bguard可以用try_lock 尝试加锁,和try_to_lock相似
            if (sbguard1.try_lock() == true) // 返回true等于拿到锁了
            {
                // 共享代码,需要加锁的地方
                this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来

                // 非共享代码,不需要加锁的,可以先解锁,这个就是unique_lock的灵活性
                sbguard1.unlock();
                // 非共享代码
                // 。。。
                // 。。。

                // 下面又开始有共享代码了,需要加锁
                sbguard1.lock();
                // 共享代码
                // 。。。。
                // ......

                // 最后可以解锁,也可以不解锁,因为会自己解锁。
            }
            else
            {
                cout << " 没有拿到锁" << endl;
            }
        }
    }

    bool outMsgLULProc(int &command)
    {
        // my_mutex1.lock();
        std::unique_lock<std::mutex> sbguard1(my_mutex1);

        std::chrono::milliseconds dura(100);
        std::this_thread::sleep_for(dura); // 休息2s

        if (!this->msgRecvQueue.empty())
        {
            // 消息不为空
            command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
            this->msgRecvQueue.pop_front();       // 移除第一个元素,但不返回

            return true;
        }
        return false;
    }

    // 把数据从消息队列中取出的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        // 用while
        while (1)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
                // 进行数据处理
                // ....
            }

            //  当全部读取完之后退出
            if (command == 99)
                break;
        }
        cout << "end" << endl;
    }

private:
    std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
    std::mutex my_mutex;         // 创建一个互斥量
    std::mutex my_mutex1;        // 第一把锁
    std::mutex my_mutex2;        // 第二把锁
};

int main(int argc, char const *argv[])
{

    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();

    system("pause");

    return 0;
}

6.3 unique_lock的成员函数

6.3.1 lock()

lock() 加锁

6.3.2 unlock()

unlock 解锁(虽然使用了defer_lock可以不用自己解锁,但是也可以自己去解锁(因为有一些非共享代码要处理,处理完可以在上锁),另外没有上锁的时候不能使用解锁)

比如:

/*
    std::unique_lock<std::mutex> sbguard1(my_mutex1, std::defer_lcok);  // 没有加锁的my_mutex1
    sbguard1.lock();    //咱们不用自己unlock

    。。。。  // 处理共享代码

    sbguard1.unlock();  // 解锁

    。。。。 // 处理非共享代码

    sbguard1.lock();    // 上锁

    。。。。 // 处理共享代码

    // sbguard1.unlock();  // 最后的解锁可有可无
*/

示例代码(会有阻塞):

#include <iostream>
#include <list>
#include <thread>
#include <mutex>

using namespace std;

class A
{
public:
    // 把收到的消息入到一个队列的线程
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100; ++i)
        {
            cout << "inMsgRecvQueue()执行,插入一个元素  " << i << endl;
            // my_mutex1.lock();
            std::unique_lock<std::mutex> sbguard1(my_mutex1, std::defer_lock);
            sbguard1.lock(); // 这边是对sbguard加锁,不用关心解锁,会自己解锁

            // 共享代码,需要加锁的地方
            this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来

            // 非共享代码,不需要加锁的,可以先解锁,这个就是unique_lock的灵活性
            sbguard1.unlock();
            // 非共享代码 
            // 。。。
            // 。。。

            // 下面又开始有共享代码了,需要加锁
            sbguard1.lock();
            // 共享代码
            // 。。。。
            // ......

            // 最后可以解锁,也可以不解锁,因为会自己解锁。

        }
    }

    bool outMsgLULProc(int &command)
    {
        // my_mutex1.lock();
        std::unique_lock<std::mutex> sbguard1(my_mutex1);

        std::chrono::milliseconds dura(100);
        std::this_thread::sleep_for(dura); // 休息2s

        if (!this->msgRecvQueue.empty())
        {
            // 消息不为空
            command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
            this->msgRecvQueue.pop_front();       // 移除第一个元素,但不返回

            return true;
        }
        return false;
    }

    // 把数据从消息队列中取出的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        // 用while
        while (1)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
                // 进行数据处理
                // ....
            }

            //  当全部读取完之后退出
            if (command == 99)
                break;
        }
        cout << "end" << endl;
    }

private:
    std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
    std::mutex my_mutex;         // 创建一个互斥量
    std::mutex my_mutex1;        // 第一把锁
    std::mutex my_mutex2;        // 第二把锁
};

int main(int argc, char const *argv[])
{

    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();

    system("pause");

    return 0;
}

6.3.3 try_lock()

尝试给互斥量加锁,如果拿不到锁,则返回false,如果拿到了锁,返回true,这个函数不会阻塞

6.3.4 release()

返回它所管理的mutex对象指针,并释放所有权;也就是说,这个unique_lock和mutex不再有关系。

严格区分unlock()和release()的区别,不要混淆。
如果原来mutex对象处于加锁状态,你有责任接管过来并负责解锁。(release返回的是原始mutex的指针)

#include <iostream>
#include <list>
#include <thread>
#include <mutex>

using namespace std;

class A
{
public:
    // 把收到的消息入到一个队列的线程
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100; ++i)
        {
            cout << "inMsgRecvQueue()执行,插入一个元素  " << i << endl;
            // my_mutex1.lock();
            std::unique_lock<std::mutex> sbguard1(my_mutex1);
            std::mutex *ptx = sbguard1.release(); //现在需要自己解锁

            this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来

            ptx->unlock(); // 需要自己解锁
        }
    }

    bool outMsgLULProc(int &command)
    {
        // my_mutex1.lock();
        std::unique_lock<std::mutex> sbguard1(my_mutex1);

        std::chrono::milliseconds dura(100);
        std::this_thread::sleep_for(dura); // 休息2s

        if (!this->msgRecvQueue.empty())
        {
            // 消息不为空
            command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
            this->msgRecvQueue.pop_front();       // 移除第一个元素,但不返回

            return true;
        }
        return false;
    }

    // 把数据从消息队列中取出的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        // 用while
        while (1)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
                // 进行数据处理
                // ....
            }

            //  当全部读取完之后退出
            if (command == 99)
                break;
        }
        cout << "end" << endl;
    }

private:
    std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
    std::mutex my_mutex;         // 创建一个互斥量
    std::mutex my_mutex1;        // 第一把锁
    std::mutex my_mutex2;        // 第二把锁
};

int main(int argc, char const *argv[])
{

    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();

    system("pause");

    return 0;
}

为什么有时候需要unlock(),因为你有lock锁住的代码段越少,执行越快,整个程序运行效率越高
有人也把锁头锁住的代码多少 称为锁的 粒度,粒度一般用粗细来描述;
a)锁住的代码少,这个粒度叫细。执行效率高
b)锁住的代码多,粒度叫粗,那执行效率就低
要学会尽量选择合适粒度的代码来进行保护,粒度太细,可能漏掉共享数据的保护,粒度太粗,影响效率。

选择合适的粒度

6.4 unique_lock所有权的传递

unique_lock 所有权的传递 mutex

std::unique_lock<std::mutex> sbguard1(my_mutex1); // 所有权权概念

sbguard1拥有my_mutex1的所有权

sbguard1可以把自己对mutex(my_mutex1)的所有权转移给其他的unique_lock对象;

所以,unique_lock对象这个mutex的所有权 属于 可以转移,但是不能复制。

方法1:std::move()

方法2:return std::unique_lock<std::mutex>

使用move转移所有权:

#include <iostream>
#include <list>
#include <thread>
#include <mutex>

using namespace std;

class A
{
public:
    // 把收到的消息入到一个队列的线程
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100; ++i)
        {
            cout << "inMsgRecvQueue()执行,插入一个元素  " << i << endl;
            // my_mutex1.lock();
            std::unique_lock<std::mutex> sbguard1(my_mutex1);
            std::unique_lock<std::mutex> sbguard2(std::move(sbguard1)); // 移动语义,现在相当于sbguard2和my_mutex1绑定到一起
                                                                        // 现在sbguard1指向空,sbguard2指向my_mutex1

            this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来

        }
    }

    bool outMsgLULProc(int &command)
    {
        // my_mutex1.lock();
        std::unique_lock<std::mutex> sbguard1(my_mutex1);

        std::chrono::milliseconds dura(100);
        std::this_thread::sleep_for(dura); // 休息2s

        if (!this->msgRecvQueue.empty())
        {
            // 消息不为空
            command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
            this->msgRecvQueue.pop_front();       // 移除第一个元素,但不返回

            return true;
        }
        return false;
    }

    // 把数据从消息队列中取出的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        // 用while
        while (1)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
                // 进行数据处理
                // ....
            }

            //  当全部读取完之后退出
            if (command == 99)
                break;
        }
        cout << "end" << endl;
    }

private:
    std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
    std::mutex my_mutex;         // 创建一个互斥量
    std::mutex my_mutex1;        // 第一把锁
    std::mutex my_mutex2;        // 第二把锁
};

int main(int argc, char const *argv[])
{

    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();

    system("pause");

    return 0;
}

另一种写法:

#include <iostream>
#include <list>
#include <thread>
#include <mutex>

using namespace std;

class A
{
public:
    std::unique_lock<std::mutex> rtn_unique_lock()
    {
        std::unique_lock<std::mutex> tmpguard(my_mutex1);
        return tmpguard; // 从函数返回一个局部的unique_lock对象是可以的
                         // 返回这种局部对象 tmpguard会导致系统生成临时unique_lock对象,并调用unique_lock的移动构造函数
    }

    // 把收到的消息入到一个队列的线程
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100; ++i)
        {
            cout << "inMsgRecvQueue()执行,插入一个元素  " << i << endl;

            std::unique_lock<std::mutex> sbguard1 = rtn_unique_lock();

            this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
        }
    }

    bool outMsgLULProc(int &command)
    {
        // my_mutex1.lock();
        std::unique_lock<std::mutex> sbguard1(my_mutex1);

        std::chrono::milliseconds dura(100);
        std::this_thread::sleep_for(dura); // 休息2s

        if (!this->msgRecvQueue.empty())
        {
            // 消息不为空
            command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
            this->msgRecvQueue.pop_front();       // 移除第一个元素,但不返回

            return true;
        }
        return false;
    }

    // 把数据从消息队列中取出的线程
    void outMsgRecvQueue()
    {
        int command = 0;
        // 用while
        while (1)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
                // 进行数据处理
                // ....
            }

            //  当全部读取完之后退出
            if (command == 99)
                break;
        }
        cout << "end" << endl;
    }

private:
    std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
    std::mutex my_mutex;         // 创建一个互斥量
    std::mutex my_mutex1;        // 第一把锁
    std::mutex my_mutex2;        // 第二把锁
};

int main(int argc, char const *argv[])
{

    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();

    system("pause");

    return 0;
}

posted @ 2022-08-27 15:05  乞力马扎罗山的雪  阅读(457)  评论(0编辑  收藏  举报