多线程Cpp

文章目录

0.基本概念

1.进程

进程:就是运行起来的可执行程序;

2.线程

每一个进程(执行起来的可执行程序),都有一个主线程,这个主线程是唯一的,自动创建的,即:一个进程中只有一个主线程;
自己创建的线程一般称为子线程;

3.并发

3.1 多进程并发

进程之间的通信:
在同一个电脑上:管道,文件,消息队列,共享内存;
在不同电脑上:socket通信技术。

3.2 多线程并发

多线程并发,单个进程中,创建多个线程;
线程像一个轻量级的进程,每一个线程都有自己独立的运行的路径,但是一个进程中的所有线程共享地址空间(即共享内存),全局变量,指针,引用都可以在线程之间传播,所以使用多线程开销远远小于多进程。,但是,多线程中,共享内存,难以保证数据一致性问题。

4.jion()

jion():加入\汇合,通俗的将就是等待或者阻塞,阻塞主线程的执行,直到子线程调用结束,然后子线程与主线汇合,然后主线程向下走;

5.detach()

detach():也就是主线程和子线程汇合,主线程与子线程各自执行,一旦detach(),这个主线程对象就会失去与这个主线程的关联,此时这个子线程就会驻留在后台运行(主线程与子线程失去联系),这个子线程就相当于被c++运行时库接管,由运行时库负责清理相关的资源。
一旦调用了detach(),就不能再用jion(),否则系统会报错。这是由于detach()之后,两条线程的执行速度不一致导致的。

6.jionable()

jionable:判断是否可以成功使用join()或detach()的。

7.注意

使用互斥量进行共享内存保护的时候,一般情况是在所需要进行保护的代码段进行lock()操作,只有lock()成功时,代码才能继续执行,否则就会一直lock()不在向下执行,直到lock()成功;

8.粒度

把锁头锁住的代码多少称为锁的粒度,粒度一般用粗细来描述;

  • 锁住的代码少,这个粒度称之为细,执行效率高;
  • 锁住的代码多,这个粒度称之为粗,执行效率低;
    所以,在进行保护时,要注意粒度的大小,粒度太细,可能漏掉共享数据的保护,粒度太粗,影响效率;

1.线程休眠

#include <iostream>
#include <thread>

using namespace std;
int main()
{
    std::this_thread::sleep_for(std::chrono::seconds(5)); //5s
    std::this_thread::sleep_for(std::chrono::seconds(5));   //1秒 = 1000毫秒=10^6微秒
    cout<<"5s\n";
    std::this_thread::sleep_for(std::chrono::microseconds(5*1000000));  //微妙
    cout<<"5s\n";
    std::this_thread::sleep_for(std::chrono::milliseconds(5000));  //毫秒
    cout<<"5s\n";
    std::this_thread::sleep_for(std::chrono::minutes(1));
    cout<<"1min\n";
    std::this_thread::sleep_for(std::chrono::hours(5));
    return 0;
}
 

2.启动线程

#include<iostream>
#include<thread>        //A
using namespace std;
void print()            //B
{
    cout<<"-----------test-------------"<<endl;
}
int main()
{
    cout<<"test start"<<endl;
    thread t(print);    //C 创建一个线程,其入口函数为print   
    t.join();           //D 等待线程结束,并加入主线程中
    return 0;
}

多线程与普通程序相比较:

  1. 第一个区别是增加了#include<thread>,A,标准C++库中对多线程支持的声明在新的头文件中;管理线程的函数和类在<thread>中声明,而保护共享数据的函数和类在其他文件中声明;
  2. print函数需要独立,B,这是因为每一个线程都必须具有一个初始函数,新线程的执行从这里开始。对于应用程序而言,初始线程是main(),但是对于其他线程,可以在std::thread对象的构造函数中指定;如:并命名为t的std::thread()的对象拥有新函数print作为其初始函数;与直接写入标准输出或是从main()调用print()不同,该程序启动了一个线程来实现,将线程数量一分为二——初始线程始于main(),而新线程始于print();
  3. 新的线程启动之后C,初始线程继续执行。如果它不等待新线程结束,它就将自顾自地继续运行到main()结束,从而结束程序,此时若新线程在main()结束之前还没有没有结束,就会导致程序出错。所以,此处调用jion()的原因,这会导致调用线程(main())等待std::thread对象相关联的线程,即本例中t;

3.传递参数

3.1传递临时对象作为线程参数

  1. 指针在detach子线程中,传递若是指针值,所以主线线程执行完成后,子线程还没执行完,此时就会出错。所以不建议在子线程采用detach时,使用指针
  2. 只要用临时构造的类对象作为参数传递给线程,那么一定能够在主线程执行完毕之前把线程函数的参数构建出来,从而确保即便detach()子线程依然能够安全运行;
  3. 若传递int这种简单类型参数,建议都是值传递,不要采用引用;(detach)
  4. 如果传递类对象,避免隐式类型转换。全部都在创建线程时,就构造临时对象,然后在函数参数里,用引用来传递对象否则系统还会多次构造一次对象;(detach)
  5. 所以,建议使用jion(),这样就不存在局部变量失效导致线程对内存的非法引用问题;
#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
class A //将整型转成A类型
{
public:
    int m_i;
    A(int a):m_i(a){cout<<"【A::A(int a)】构造函数执行\t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
    A(const A &a):m_i(a.m_i){cout<<"【A::A(const A)】构造函数执行\t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
    ~A(){cout<<"【A::A(int a)】析构造函数执行\t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
};
void myprint(const int i,const A &pmybuf) //!采用引用,否则会被构造三次:此处需要使用const 否则出错:
                                            //1.thread myobject(myprint,mvar,A(mysecondpar))中创建A(mysecondpar)临时对象时,调用一次构造函数;
                                            //2. void myprint(const int i,const A &pmybuf)虽然这里采用引用传递,但是编译器在构造时,采用拷贝方式,
                                            //目的是防止,在多线程中线程之间执行速度不一导致出现错误。
                                            //3.?
{
    cout<<&pmybuf<<endl;
    return;
}

int main()
{
    int mvar=1;
    int myseconpar=12;
    cout<<"主线程id:"<<this_thread::get_id()<<endl;
    //thread myobject(myprint,mvar,myseconpar);//易出错!!
                                                    //myseconpar->A隐式转换是在调用子线程时执行,从而可能导致,main函数执行完毕后,myseconpar对象已经销毁,进而导致转换出错!
    thread myobject(myprint,mvar,A(myseconpar));//myseconpar->A转换是在主线程中转换.
    // myobject.detach();
    myobject.join();
    return 0;
}

4.线程id

线程id:id是一个数字,不同的线程,它的线程id必然不同;线程id可以同过c++标准库中的函数获取。std::this_thread::get_id()线程对象.get_id();

5.传递类对象、智能指针、类成员函数和类对象作为线程参数

5.1 传递类对象

std::ref()传递引用对象;

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
class A //将整型转成A类型
{
public:
    int m_i;
    A(int a):m_i(a){cout<<"【A::A(int a)】构造函数执行\t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
    A(const A &a):m_i(a.m_i){cout<<"【A::A(const A)】构造函数执行\t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
    ~A(){cout<<"【A::A(int a)】析构造函数执行\t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
};

//!推荐使用
void myprint(A &pmybuf) 
                                            
{
    cout<<&pmybuf<<endl;
    return;
}

int main()
{
    int mvar=1;
    int myseconpar=12;
    cout<<"主线程id:"<<this_thread::get_id()<<endl;
    A myobj(10);
    std::thread mytobj(myprint,std::ref(myobj));//std::ref()引用传递
    mytobj.join();
    return 0;
}

5.2 传递智能指针

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
class A //将整型转成A类型
{
public:
    int m_i;
    A(int a):m_i(a){cout<<"【A::A(int a)】构造函数执行\t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
    A(const A &a):m_i(a.m_i){cout<<"【A::A(const A)】构造函数执行\t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
    ~A(){cout<<"【A::A(int a)】析构造函数执行\t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
   
};

//!智能指针
void myprint(unique_ptr<int> ptr_)                                             
{
    cout<<"智能指针传递测试"<<endl;
    return;
}

int main()
{
    cout<<"主线程id:"<<this_thread::get_id()<<endl;
    unique_ptr<int> ptr(new int (100));
    //std::thread mytobj(myprint,ptr);  //不能直接采用这种方式进行传递,需要依赖std::move()
    std::thread mytobj(myprint,std::move(ptr)); //采用std::move()后,将会将指针ptr传递到ptr_但是,此时ptr就会置空(move移动);
    mytobj.join();
    return 0;
}

5.3 类成员函数

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
class A //将整型转成A类型
{
public:
    int m_i;
    A(int a):m_i(a){cout<<"【A::A(int a)】构造函数执行\t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
    A(const A &a):m_i(a.m_i){cout<<"【A::A(const A)】构造函数执行\t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
    ~A(){cout<<"【A::A(int a)】析构造函数执行\t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}

    //! 成员函数作为子线程入口函数:
    void thread_work(int a)
    {
        cout<<"子线程成员函数执行:\t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;
    }
};

int main()
{
    cout<<"主线程id:"<<this_thread::get_id()<<endl;
    A myobj(10);
    std::thread mytobj(&A::thread_work,&myobj,20); //表示的是myobj对象的thread_work成员函数作为多线程对象的   
                                                    //mytobj的入口函数,并传递参数20;
                                                    //!此处&myobj等价于std::ref(myobj)

    mytobj.join();
    return 0;
}

5.3 类对象

需要重载运算符();

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
class A //将整型转成A类型
{
public:
    int m_i;
    A(int a):m_i(a){cout<<"【A::A(int a)】构造函数执行\t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
    A(const A &a):m_i(a.m_i){cout<<"【A::A(const A)】构造函数执行\t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
    ~A(){cout<<"【A::A(int a)】析构造函数执行\t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}

    //! 成员函数作为子线程入口函数:
    void thread_work(int a)
    {
        cout<<"子线程成员函数thread_work执行:\t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;
    }
    void operator()(int num) //任意A对象都可以作为线程的入口函数;
    {
        cout<<"子线程operator()执行:\t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;
    }
};

int main()
{
    cout<<"主线程id:"<<this_thread::get_id()<<endl;

    A myobj(10);    //类对象
    std::thread mytobj(myobj,20);   //类对象作为线程入口
    //std::thread mytobj(std::ref(myobj),20);  //采用引用的形式
    mytobj.join();
    return 0;
}

6.数据共享问题:

6.1 只读数据

是安全稳定的,不需要特别处理。

6.2 读写数据

数据读写时,需要进行相关处理,否则容易使得程序崩溃。

//此代码存在数据共享冲突!
#include <iostream>
#include <thread>
#include <list>
using namespace std;
class A
{
public:
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 1000000; ++i)
        {
            cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
            msgRecvQueue.push_back(i);
        }
    }
    void outMsgRecvQueue()
    {
        for (int i = 0; i < 1000000; ++i)
        {
            if (!msgRecvQueue.empty())
            {
                int commnd = msgRecvQueue.front(); //返回第一个元素
                msgRecvQueue.pop_front();          //移除第一个元素
            }
            else
            {
                cout << "outMsgRecvQueue()消息为空!" << i << endl;
            }
        }

        cout << "finished!" << endl;
    }

private:
    list<int> msgRecvQueue;
};
int main()
{
    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
   
    myOutMsgObj.join();
    myInMsgObj.join();
    return 0;
}

7.互斥量

互斥量是个类对象。相当于一把锁,多个线程尝试用lock()成员函数来加锁这把锁头,只有一个线程能锁定成功(成功的标志是lock()函数返回),若果没锁成功,那么就会在lock()这里不断尝试上锁;

7.1 互斥量的使用

#include<mutex>
lock(),unlock();

先lock(),操作共享数据,unlock();
注意:

  1. lock()与unlock()要成对使用,有lock()必须要有unlock(),每调用一次lock(),必然应该调用一次unlock();
  2. 不应该也不允许调用1次lock()却调用了2次unlock(),也不允许调用了两次lock()却调用了一次unlock(),这些非对称数量的调用,都会导致代码不稳定,甚至崩溃。
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class A
{
public:
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 10000; ++i)
        {
            cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;

            //写数据时进行保护
            my_mutex.lock();
            msgRecvQueue.push_back(i);  //保证在插入数据时,锁住程序,不被修改。
            //写完后,进行解锁
            my_mutex.unlock();
        }
    }
    bool outMsgLULProc(int &command)
    {
        my_mutex.lock();
        if (!msgRecvQueue.empty())
        {
            command = msgRecvQueue.front(); //返回第一个元素
            msgRecvQueue.pop_front();          //移除第一个元素
            my_mutex.unlock();
            return true;
        }
        my_mutex.unlock();
        return false;
    }
    void outMsgRecvQueue()
    {
        int command=0;
        for(int i=0;i<10000;++i)
        {
            bool result=outMsgLULProc(command);
            if(result==true)
            {
                cout<<"outMsgRecvQueue()执行,取出一个元素"<<endl;
            }
            else
            {
                cout<<"outMsgRecvQueue()为空。"<<endl;
            }
        }
        

        cout << "finished!" << endl;
    }

private:
    list<int> msgRecvQueue;
    std::mutex my_mutex;
};
int main()
{
    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();
    return 0;
}

7.2 std::lock_guard()

std::lock_guard()类模板,直接取代lock()与unlock(),用了lock_guard()之后,就不能在使用lock()与unlock();

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class A
{
public:
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 10000; ++i)
        {
            cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;

            {
                std::lock_guard<std::mutex> guard(my_mutex);
                //写数据时进行保护
                //my_mutex.lock();
                msgRecvQueue.push_back(i); //保证在插入数据时,锁住程序,不被修改。
                //写完后,进行解锁
                //my_mutex.unlock();
            }//通过此对大括号,提前解锁。guard相当于一个局部对象,提前析构,即解锁;

        }
    }
    bool outMsgLULProc(int &command)
    {
        lock_guard<std::mutex> guard(my_mutex); //guard:构造函数执行了mutex::lock
                                                //guard:析构函数执行了mutex::unlock();
                                                // my_mutex.lock();
        if (!msgRecvQueue.empty())
        {
            command = msgRecvQueue.front(); //返回第一个元素
            msgRecvQueue.pop_front();           //移除第一个元素
            my_mutex.unlock();
            return true;
        }
        //my_mutex.unlock();
        return false;
    }
    void outMsgRecvQueue()
    {
        int command = 0;
        for (int i = 0; i < 10000; ++i)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
            }
            else
            {
                cout << "outMsgRecvQueue()为空。" << endl;
            }
        }

        cout << "finished!" << endl;
    }

private:
    list<int> msgRecvQueue;
    std::mutex my_mutex;
};
int main()
{
    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();
    return 0;
}

8.死锁

8.1概念

假如现在存在两把锁(死锁这个问题是至少两个锁头也就是两个互斥量才能产生)金锁(Jinlock),银锁(Yinlock),两个线程A,B,线程A执行的时候,这个线程先锁金锁,把金锁lock()成功,然后它准备去锁银锁,此时可能由于上下文切换,导致线程A去执行其他任务。
同样,线程B执行了,这个线程先锁银锁,因为银锁没有被锁,所以银锁会lock()成功,线程B要去锁金锁。
此种情况就导致了死锁的发生,线程A锁住金锁准备锁银锁,但是银锁被线程B锁定,此时线程A需要等待线程B解锁才能继续执行,但巧合的是,线程B锁住了银锁,需要进行锁金锁,但是线程A将金锁锁住,线程B需要等待线程A执行完毕,进行解锁之后才能继续执行,出现死锁;

8.2 死锁演示

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class A
{
public:
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100000; ++i)
        {
            cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;

            //!通过lock_guard()进行死锁演示:
            std::lock_guard<std::mutex> guard1(my_mutex1); //! my_mutex1 先上锁,然后my_mutex2再上锁。
            std::lock_guard<std::mutex> guard2(my_mutex2);

            // my_mutex1.lock(); 
            
            // my_mutex2.lock();

            msgRecvQueue.push_back(i); //保证在插入数据时,锁住程序,不被修改。
            
            // my_mutex2.unlock(); //! 解锁的先后顺序无关;
            // my_mutex1.unlock();
        }
    }
    bool outMsgLULProc(int &command)
    {
        std::lock_guard<std::mutex> guard1(my_mutex2);//! my_mutex2 先上锁,然后my_mutex1再上锁。
        std::lock_guard<std::mutex> guard2(my_mutex1);
        // my_mutex2.lock(); 
        // my_mutex1.lock();
        if (!msgRecvQueue.empty())
        {
            command = msgRecvQueue.front(); //返回第一个元素
            msgRecvQueue.pop_front();           //移除第一个元素
            // my_mutex1.unlock();
            // my_mutex2.unlock(); //! 解锁的先后顺序无关;
            return true;
        }
        // my_mutex1.unlock();
        // my_mutex2.unlock();
        return false;
    }
    void outMsgRecvQueue()
    {
        int command = 0;
        for (int i = 0; i < 100000; ++i)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
            }
            else
            {
                cout << "outMsgRecvQueue()为空。" << endl;
            }
        }

        cout << "finished!" << endl;
    }

private:
    list<int> msgRecvQueue;
    std::mutex my_mutex1; //! 死锁产生的前提是需要两把锁,和两个线程!
    std::mutex my_mutex2;
};
int main()
{
    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();
    return 0;
}

死锁的解决方法:只要保证这两个互斥量上锁的顺序一致就不会死锁,例如,现在有两互斥量锁std::mutex mymutex1与std::mutex mymutex2;每次上锁的时,都先锁std::mutex mymutex1,即mymutex1.lock(),后锁mymutex2.lock()就能够避免死锁的产生;

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class A
{
public:
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100000; ++i)
        {
            cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;

            //!通过lock_guard()进行死锁演示:
            std::lock_guard<std::mutex> guard1(my_mutex1); //! my_mutex1 先上锁,然后my_mutex2再上锁。
            std::lock_guard<std::mutex> guard2(my_mutex2);

            // my_mutex1.lock(); 
            
            // my_mutex2.lock();

            msgRecvQueue.push_back(i); //保证在插入数据时,锁住程序,不被修改。
            
            // my_mutex2.unlock(); //! 解锁的先后顺序无关;
            // my_mutex1.unlock();
        }
    }
    bool outMsgLULProc(int &command)
    {
        //! 为了避免死锁,所以要保证这两个互斥量上锁的顺序一致,在inMsgRecvQueue中先锁my_mutex1,故,此处也先锁my_mutex1;
        std::lock_guard<std::mutex> guard2(my_mutex1);
        std::lock_guard<std::mutex> guard1(my_mutex2);
        
        // my_mutex1.lock();
        // my_mutex2.lock(); 
        if (!msgRecvQueue.empty())
        {
            command = msgRecvQueue.front(); //返回第一个元素
            msgRecvQueue.pop_front();           //移除第一个元素
            // my_mutex1.unlock();
            // my_mutex2.unlock(); //! 解锁的先后顺序无关;
            return true;
        }
        // my_mutex1.unlock();
        // my_mutex2.unlock();
        return false;
    }
    void outMsgRecvQueue()
    {
        int command = 0;
        for (int i = 0; i < 100000; ++i)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
            }
            else
            {
                cout << "outMsgRecvQueue()为空。" << endl;
            }
        }

        cout << "finished!" << endl;
    }

private:
    list<int> msgRecvQueue;
    std::mutex my_mutex1; //! 死锁产生的前提是需要两把锁,和两个线程!
    std::mutex my_mutex2;
};
int main()
{
    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();
    return 0;
}

9.std::lock()函数模板 <用的比较少>

一次锁住两个或者两个以上的互斥量(至少两个,多了不限,1个不行);它在多线程中不存在因为锁的顺序导致死锁的风险存在,这是因为如果其中一个互斥量中有一个没有锁住,它就会释放所有已经锁住的互斥量(这样就能有效的避免死锁的存在),然后重复上锁直到所有的锁都锁上才向下执行,否则继续释放已上锁的锁;

10.std::lock_guard的std::adopt_lock参数

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

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class A
{
public:
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100000; ++i)
        {
            cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
            std::lock(my_mutex1,my_mutex2);//std::lock()函数模板,用来一次锁上两个或者两个以上的锁;为了避免在后面忘记解锁,
            //采用带有std::adopt_lock()的std::lock_guard(),std::adopt_lock()的作用就是,不对已经上锁的对象进行再次上锁。

            std::lock_guard<std::mutex> guard1(my_mutex1,std::adopt_lock);
            std::lock_guard<std::mutex> guard2(my_mutex2,std::adopt_lock);

            // my_mutex1.lock(); 
            // my_mutex2.lock();

            msgRecvQueue.push_back(i); //保证在插入数据时,锁住程序,不被修改。
            
            // my_mutex2.unlock(); //! 解锁的先后顺序无关;
            // my_mutex1.unlock();
        }
    }
    bool outMsgLULProc(int &command)
    {
        //! 为了避免死锁,所以要保证这两个互斥量上锁的顺序一致,在inMsgRecvQueue中先锁my_mutex1,故,此处也先锁my_mutex1;    
        std::lock(my_mutex1,my_mutex2);

        std::lock_guard<std::mutex> guard1(my_mutex1,std::adopt_lock);
        std::lock_guard<std::mutex> guard2(my_mutex2,std::adopt_lock);
        // my_mutex1.lock();
        // my_mutex2.lock(); 
        if (!msgRecvQueue.empty())
        {
            command  = msgRecvQueue.front(); //返回第一个元素
            msgRecvQueue.pop_front();           //移除第一个元素
            // my_mutex1.unlock();
            // my_mutex2.unlock(); //! 解锁的先后顺序无关;
            return true;
        }
        // my_mutex1.unlock();
        // my_mutex2.unlock();
        return false;
    }
    void outMsgRecvQueue()
    {
        int command = 0;
        for (int i = 0; i < 100000; ++i)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
            }
            else
            {
                cout << "outMsgRecvQueue()为空。" << endl;
            }
        }

        cout << "finished!" << endl;
    }

private:
    list<int> msgRecvQueue;
    std::mutex my_mutex1; //! 死锁产生的前提是需要两把锁,和两个线程!
    std::mutex my_mutex2;
};
int main()
{
    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();
    return 0;
}

11.unique_lock详解

11.1 unique_lock一般使用

unique_lock是一个类模板,工作中一般推荐使用lock_guard;lock_guard取代了lock()与unlock();
unique_lock相对于lock_guard而言灵活很多,但是效率差一点,并且内存占用多一点;
在一般不带参数使用时,unique_lock()lock_guard()是相同的,unique_lock()的灵活性主要体现在第二参数上。

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class A
{
public:
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100000; ++i)
        {
            cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
            std::unique_lock<std::mutex> guard1(my_mutex1);
    
            msgRecvQueue.push_back(i); //保证在插入数据时,锁住程序,不被修改。
        }
    }
    bool outMsgLULProc(int &command)
    {  
        std::unique_lock<std::mutex> guard1(my_mutex1);
        if (!msgRecvQueue.empty())
        {
            command = msgRecvQueue.front(); //返回第一个元素
            msgRecvQueue.pop_front();           //移除第一个元素
           
            return true;
        }
        return false;
    }
    void outMsgRecvQueue()
    {
        int command = 0;
        for (int i = 0; i < 100000; ++i)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
            }
            else
            {
                cout << "outMsgRecvQueue()为空。" << endl;
            }
        }

        cout << "finished!" << endl;
    }

private:
    list<int> msgRecvQueue;
    std::mutex my_mutex1; 
  
};
int main()
{
    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();
    return 0;
}

11.2 unique_lock的第二参数

11.2.1 std::adopt_lock()

使用std::adopt_lock()的前提是一定先lock(),否则会报异常(windows);

  1. std::lock_guard<std::mutex> guard1(my_mutex1,std::adopt_lock); adopt_lock起标记作用。std::adopt_lock:表示这个互斥量my_mutex1已经被lock()了,(在使用adopt_lock作为第二参数时,必须确保所需互斥量(my_mutex1)已经在前面lock()了,否则报异常);std::adopt_lock()的标记效果就是:假设调用方线程已经拥有了互斥量的所有权(已经lock成功);通知lock_guard()不需要在构造函数中lock()这个互斥量;
  2. std::unique_lock也可以带std::adopt_lock标记,含义与std::lock_guard相同,就是不希望再unique_lock()的构造函数中lock()这个mutex;
...
my_mutex1.lock();//先要lock()后,才能用unique_lock()的std::adopt_lock()参数
std::lock_guard<std::mutex> guard1(my_mutex1,std::adopt_lock);
...

11.2.2 std::try_lock();

使用try_to_lock的前提是一定不要先lock(),否则会报异常(windows);
我们会尝试用mutex的lock()去锁这个互斥量,但如果没有锁定成功,就会立即返回,并不会阻塞在lock();

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include<string>
using namespace std;
class A
{
public:
    void inMsgRecvQueue()
    {
        int j = 0;
        for (int i = 0; i < 100000; ++i)
        {
            //! std::try_to_lock
            std::unique_lock<std::mutex> guard1(my_mutex1, std::try_to_lock);
            if (guard1.owns_lock())
            {
                cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
                cout << "拿到锁,开始插入第" + to_string(j) << "个数据" << endl;
                msgRecvQueue.push_back(i); //保证在插入数据时,锁住程序,不被修改。
                j++;
            }
            else //当没拿到锁的时候,会执行其他事,而不是一直等待lock
            {
                cout << "没拿到锁,干点其他事" << endl;
            }
          
        }
    }
    bool outMsgLULProc(int& command)
    {
        std::unique_lock<std::mutex> guard1(my_mutex1);
        std::this_thread::sleep_for(std::chrono::milliseconds(200));//休眠200ms
        if (!msgRecvQueue.empty())
        {
            command = msgRecvQueue.front(); //返回第一个元素
            msgRecvQueue.pop_front();           //移除第一个元素

            return true;
        }
        return false;
    }
    void outMsgRecvQueue()
    {
        int command = 0;
        for (int i = 0; i < 100000; ++i)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
            }
            else
            {
                cout << "outMsgRecvQueue()为空。" << endl;
            }
        }

        cout << "finished!" << endl;
    }

private:
    list<int> msgRecvQueue;
    std::mutex my_mutex1;

};
int main()
{
    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();
    return 0;
}

11.2.3 std::defer_lock;

std::defer_lock的前提是一定不要先lock(),否则会报异常(windows);
std::defer_lock:表示并没有给mutex加锁,即初始话一个没有加锁的mutex,通常结合unique_lock的成员函数使用;

12. uniuqe_lock的成员函数

12.1 lock() 加锁

12.2 unlock()解锁

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include<string>
using namespace std;
class A
{
public:
    void inMsgRecvQueue()
    {
        int j = 0;
        for (int i = 0; i < 100000; ++i)
        {
            cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
            std::unique_lock<std::mutex> guard1(my_mutex1, std::defer_lock);//my_mutex1未加锁
            guard1.lock();//调用成员函数加锁,不需要在调用unlock解锁,这是由于guard1在析构时解锁;

            //guard1.unlock();//对于执行非保护数据时,可以采用成员函数unlock先进行解锁操作。

            //不需要保护代码段...

            //guard1.lock();上锁
            msgRecvQueue.push_back(i); 
        }
    }
    bool outMsgLULProc(int& command)
    {
        std::unique_lock<std::mutex> guard1(my_mutex1);
       
        if (!msgRecvQueue.empty())
        {
            command = msgRecvQueue.front(); //返回第一个元素
            msgRecvQueue.pop_front();           //移除第一个元素

            return true;
        }
        return false;
    }
    void outMsgRecvQueue()
    {
        int command = 0;
        for (int i = 0; i < 100000; ++i)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
            }
            else
            {
                cout << "outMsgRecvQueue()为空。" << endl;
            }
        }

        cout << "finished!" << endl;
    }

private:
    list<int> msgRecvQueue;
    std::mutex my_mutex1;

};
int main()
{
    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();
    return 0;
}

11.2.3 try_lock():

try_lock():尝试给互斥量加锁,如果拿不到锁,就返回false,否则返回true,这个函数为非阻塞函数.

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include<string>
using namespace std;
class A
{
public:
    void inMsgRecvQueue()
    {
        int j = 0;
        for (int i = 0; i < 100000; ++i)
        {
            cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
            std::unique_lock<std::mutex> guard1(my_mutex1, std::defer_lock);//my_mutex1未加锁
           
            if(guard1.try_lock()==true)//成员函数try_lock
            {
                msgRecvQueue.push_back(i); 
            }
            else
            {
                cout<<"没拿到锁,干点其他事!"<<endl;
            }
            
        }
    }
    bool outMsgLULProc(int& command)
    {
        std::unique_lock<std::mutex> guard1(my_mutex1);

        std::this_thread::sleep_for(std::chrono::milliseconds(2000));
        if (!msgRecvQueue.empty())
        {
            command = msgRecvQueue.front(); //返回第一个元素
            msgRecvQueue.pop_front();           //移除第一个元素

            return true;
        }
        return false;
    }
    void outMsgRecvQueue()
    {
        int command = 0;
        for (int i = 0; i < 100000; ++i)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
            }
            else
            {
                cout << "outMsgRecvQueue()为空。" << endl;
            }
        }

        cout << "finished!" << endl;
    }

private:
    list<int> msgRecvQueue;
    std::mutex my_mutex1;

};
int main()
{
    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();
    return 0;
}

11.2.3 release():

release():返回它所管理的mutex对象指针,并释放所有权;也就是说,这个unique_lock和mutex不在有关系。此时如果原来mutex对象处于加锁状态,需要手动解锁,否则出错!

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include <string>
using namespace std;
class A
{
public:
    void inMsgRecvQueue()
    {
        int j = 0;
        for (int i = 0; i < 100000; ++i)
        {
            cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
            std::unique_lock<std::mutex> guard1(my_mutex1); //构造成功时,加锁,加锁成功才能继续执行下面代码,否则等待加锁;

            std::mutex *ptx=guard1.release();//!guard1释放my_mutex1,两者之间不存在任何关系,
                                            //!此时需要手动解锁,因为unique_lock构造函数执行成功后,
                                            //!才能执行到这一步,所以需要进行手动解锁;
            
            msgRecvQueue.push_back(i); //!保护数据段
            ptx->unlock(); //解锁
            
        }
    }
    bool outMsgLULProc(int &command)
    {
        std::unique_lock<std::mutex> guard1(my_mutex1);

        // std::this_thread::sleep_for(std::chrono::milliseconds(2000));
        if (!msgRecvQueue.empty())
        {
            command = msgRecvQueue.front(); //返回第一个元素
            msgRecvQueue.pop_front();       //移除第一个元素

            return true;
        }
        return false;
    }
    void outMsgRecvQueue()
    {
        int command = 0;
        for (int i = 0; i < 100000; ++i)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
            }
            else
            {
                cout << "outMsgRecvQueue()为空。" << endl;
            }
        }

        cout << "finished!" << endl;
    }

private:
    list<int> msgRecvQueue;
    std::mutex my_mutex1;
};
int main()
{
    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();
    return 0;
}

11.2.4 所有权

std::unique_lock<std::mutex> guard1(my_mutex1);guard1拥有my_mutex1的所有权,guard1可以将机子对my_mutex1的所有权转移给其他的unique_lock对象,unique_lock对象的所有权只能转移,不能复制!!
转移所有权的方式:

  1. std::move()
  2. return std::unique_lock<std::mutex>
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include <string>
using namespace std;
class A
{
public:

    //! 移动所有权方式2
    std::unique_lock<std::mutex> rtn_unique_lock()
    {
        std::unique_lock<std::mutex> tempguard(my_mutex1);
        return tempguard;//返回这种局部对象tempguard会导致系统生成临时unique_lock对象,并调用unique_lock的移动构造函数;
    }
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100000; ++i)
        {
            cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
            //std::unique_lock<std::mutex> guard1(my_mutex1); //构造成功时,加锁,加锁成功才能继续执行下面代码,否则等待加锁;
            
            //!移动所有权方式1
            //std::unique_lock<std::mutex> guard2(std::move(guard1)); //!将guard1对my_mutex1所有权转移给guard2
            //!移动所有权方式2
            std::unique_lock<std::mutex> guard1=rtn_unique_lock(); 
            msgRecvQueue.push_back(i); //!保护数据段    
        }
    }
    bool outMsgLULProc(int &command)
    {
        std::unique_lock<std::mutex> guard1(my_mutex1);

        // std::this_thread::sleep_for(std::chrono::milliseconds(2000));
        if (!msgRecvQueue.empty())
        {
            command = msgRecvQueue.front(); //返回第一个元素
            msgRecvQueue.pop_front();       //移除第一个元素

            return true;
        }
        return false;
    }
    void outMsgRecvQueue()
    {
        int command = 0;
        for (int i = 0; i < 100000; ++i)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
            }
            else
            {
                cout << "outMsgRecvQueue()为空。" << endl;
            }
        }

        cout << "finished!" << endl;
    }

private:
    list<int> msgRecvQueue;
    std::mutex my_mutex1;
};
int main()
{
    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();
    return 0;
}

13.单例设计模式

单例设计模式,使用频率比较高。单例设计模式,是指某些特殊的类,属于该类的对象只能创建一个,多了创建不了。

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include <string>
using namespace std;
class MyCAS //这是一个但单例类
{
private:
    MyCAS(){} //私有化构造函数
private:
    static MyCAS *m_instance;//静态成员变量,相当于公共空间,所有类对象都可以进行调用,用来释放m_instance空间;
public:
    static MyCAS *GetIntance()
    {
        if(m_instance==NULL)
        {
            m_instance =new MyCAS();
            static CGarhuishou cl;//静态对象在程序执行完成后进行析构;
            //!CGarhuishou在后面进行定义的,为什么在前面没有报错?
        }
        return m_instance;
    }
    class CGarhuishou //嵌套类,用来释放对象
    {
    public:
        ~CGarhuishou()
        {
           if(MyCAS::m_instance)
           {
               delete MyCAS::m_instance;
               MyCAS::m_instance=NULL;
           }
        }
    };
    void fun()
    {
        cout<<"test"<<endl;
    }
};
MyCAS *MyCAS::m_instance=NULL;
int main()
{
    MyCAS *p_a=MyCAS::GetIntance();//创建一个对象,返回该类(MyCAS)对象的指针;
    MyCAS *p_b=MyCAS::GetIntance();//由于m_instance==NULL才进行构造,所以p_a与p_b是指向同一个对象;
    p_a->fun();
    //或
    MyCAS::GetIntance()->fun();
}

上面程序在单个线程中使用时,是没问题的,但是若是多线程执行时,可能引发错误,错误的原因是:

if(m_instance==NULL)
        {
            m_instance =new MyCAS();
            static CGarhuishou cl;
        }
return m_instance;
//例如:存在两个线程,线程A与线程B,当线程A执行if(m_instance==NULL)时,此时进行上下文切换,导致线程A还没来得及执行 \
m_instance =new MyCAS();时,线程A执行其他程序,此时m_instance==NULL,倘若此时线程B也执行if(m_instance==NULL),此时就可\
能导致 m_instance =new MyCAS(),执行了两次。则此时需要通过互斥量进行保护;
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include <string>
using namespace std;
std::mutex resource_mutex;

class MyCAS //这是一个但单例类
{
private:
    MyCAS() {} //私有化构造函数
    
private:

    static MyCAS *m_instance; //静态成员变量,相当于公共空间,所有类对象都可以进行调用,用来释放m_instance空间;
   
public:
    static MyCAS *GetIntance()
    {
        std::unique_lock<std::mutex> guard(resource_mutex);//这样虽然可以避免程序出错,但是影响执行效率。
                                                            //因为每执行一次GetIntance就需要进行上锁,但是
                                                            //我们只是希望在初始化的时候进行上锁,所以这样会降低
                                                            //程序的执行效率;
        if (m_instance == NULL)
        {
            m_instance = new MyCAS();
            static CGarhuishou cl; //静态对象在程序执行完成后进行析构;
            //!CGarhuishou在后面进行定义的,为什么在前面没有报错?
        }
        return m_instance;
    }
    class CGarhuishou //嵌套类,用来释放对象
    {
    public:
        ~CGarhuishou()
        {
            if (MyCAS::m_instance)
            {
                delete MyCAS::m_instance;
                MyCAS::m_instance = NULL;
            }
        }
    };
    void fun()
    {
        cout << "test" << endl;
    }
};
MyCAS *MyCAS::m_instance = NULL;
//线程入口函数
void mythread()
{
    cout << "线程开始执行....." << endl;
    MyCAS *p_a = MyCAS::GetIntance();   //此时可能容易出问题;
    cout << "线程开始执行....." << endl;
}
int main()
{
    std::thread myobj1(mythread);
    std::thread myobj2(mythread);
    myobj1.join();
    myobj2.join();
}

解决方案:

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include <string>
using namespace std;
std::mutex resource_mutex;

class MyCAS //这是一个但单例类
{
private:
    MyCAS() {} //私有化构造函数

private:
    static MyCAS *m_instance; //静态成员变量,相当于公共空间,所有类对象都可以进行调用,用来释放m_instance空间;

public:
    static MyCAS *GetIntance()
    {
        //! 提高效率:
        //! 如果if(m_instance != NULL)条件成立,则肯定表示m_instance已经被new过了;
        //! 如果if(m_instance == NULL)条件成立,不代表m_instance一定没被new过->上下文切换!!
        if(m_instance == NULL) //双重锁定(双重检查)
        {
            std::unique_lock<std::mutex> guard(resource_mutex);
            if (m_instance == NULL)
            {
                m_instance = new MyCAS();
                static CGarhuishou cl; //静态对象在程序执行完成后进行析构;
                //!CGarhuishou在后面进行定义的,为什么在前面没有报错?
            }
        }
        
        return m_instance;
    }
    class CGarhuishou //嵌套类,用来释放对象
    {
    public:
        ~CGarhuishou()
        {
            if (MyCAS::m_instance)
            {
                delete MyCAS::m_instance;
                MyCAS::m_instance = NULL;
            }
        }
    };
    void fun()
    {
        cout << "test" << endl;
    }
};
MyCAS *MyCAS::m_instance = NULL;
//线程入口函数
void mythread()
{
    cout << "线程开始执行....." << endl;
    MyCAS *p_a = MyCAS::GetIntance(); //此时可能容易出问题;
    cout << "线程开始执行....." << endl;
}
int main()
{
    std::thread myobj1(mythread);
    std::thread myobj2(mythread);
    myobj1.join();
    myobj2.join();
}

14.std::call_once()

std::call_once()该函数的第二个参数是一个函数名 a();
std::call_once()的功能是保证函数a()只会被调用一次(无论是单线程中多次被调用,或者在多线程中被多次调用);
std::call_once()具备互斥量这种能力,而且在效率上,比互斥量的消耗的资源更少;
std::call_once()需要与一个标记结合使用,这个标记std::once_flag;std::once_flag其实是一个结构,std::call_once()就是通过这个标记来决定对应a()函数对否执行,调用std::call_once()成功后,std::call_once()就会把这个标记设置为一种已调用的状态,后续再次调用std::call_once(),只要once_flag被设置为了“已调用”状态,那么对应的函数a()就不会再被执行;

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include <string>
using namespace std;
std::mutex resource_mutex;
std::once_flag g_flag;      //! for call_once()
class MyCAS //这是一个但单例类
{
    static void CreatInstance() //只被调用一次
    {
        m_instance = new MyCAS();
        static CGarhuishou cl; //静态对象在程序执行完成后进行析构->delete m_instance
    }
private:
    MyCAS() {} //私有化构造函数

private:
    static MyCAS *m_instance; //静态成员变量,相当于公共空间,所有类对象都可以进行调用,用来释放m_instance空间;

public:
    static MyCAS *GetIntance()
    {
        //! 两个线程同时执行到这里,其中一个线程要等待另一个线程执行完CreatInstance;
        std::call_once(g_flag,CreatInstance);
        return m_instance;
    }
    class CGarhuishou //嵌套类,用来释放对象
    {
    public:
        ~CGarhuishou()
        {
            if (MyCAS::m_instance)
            {
                delete MyCAS::m_instance;
                MyCAS::m_instance = NULL;
            }
        }
    };
    void fun()
    {
        cout << "test" << endl;
    }
};
MyCAS *MyCAS::m_instance = NULL;
//线程入口函数
void mythread()
{
    cout << "线程开始执行....." << endl;
    MyCAS *p_a = MyCAS::GetIntance(); //此时可能容易出问题;
    cout << "线程开始执行....." << endl;
}
int main()
{
    std::thread myobj1(mythread);
    std::thread myobj2(mythread);
    myobj1.join();
    myobj2.join();
}

15.条件变量std::condition_variable、wait()、notify_one(),notify_all()

15.1 条件变量std::condition_variable、wait()、notify_one()

假若现在存在两个线程A与线程B,线程B向线程A发送一个信号时,线程A才开始向下执行。
std::condition_variable实际上是一个类,是一个和条件相关的一个类,等待一个条件达成,才能执行相关操作;这个类是需要和互斥量来配合工作的,用的时候我们要生成这个类的对象;

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable> //! for condition_variable
#include <list>
#include <string>
using namespace std;
class A
{
public:
    //! 移动所有权方式2
    std::unique_lock<std::mutex> rtn_unique_lock()
    {
        std::unique_lock<std::mutex> tempguard(my_mutex1);
        return tempguard; //返回这种局部对象tempguard会导致系统生成临时unique_lock对象,并调用unique_lock的移动构造函数;
    }
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100000; ++i)
        {
            cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
            std::unique_lock<std::mutex> guard1 = rtn_unique_lock();

            msgRecvQueue.push_back(i); //!保护数据段
            my_cond.notify_one();      //!假如此时outMsgRecvQueue()正在处理一个事物,而不是正卡在wait()哪里等待被唤醒,
                                         //!那么此时这个notify_one()这个调用也许就没用的!

                                    
        }
    }
    // bool outMsgLULProc(int &command)
    // {
        //双重锁定,双重验证,提高效率;
    //     if (!msgRecvQueue.empty())
    //     {
    //         std::unique_lock<std::mutex> guard1(my_mutex1);

    //         // std::this_thread::sleep_for(std::chrono::milliseconds(2000));
    //         if (!msgRecvQueue.empty())
    //         {
    //             command = msgRecvQueue.front(); //返回第一个元素
    //             msgRecvQueue.pop_front();       //移除第一个元素

    //             return true;
    //         }
    //         return false;
    //     }
    // }
    void outMsgRecvQueue()
    {
        int command = 0;
        while (true)
        {
            std::unique_lock<std::mutex> guard(my_mutex1);

            //! wait()用来等待一个条件:
            //!如果第二个参数lambda表达式返回值是一个true,那么wait()直接返回,程序向下执行;
            //!如果第二个参数lambda表达式返回值是一个false,那么wait()将解锁互斥量,并堵塞到本行
            //!直到其他某个线程调用notify_one()成员函数为止;
            //!如果wait()没有第二个参数:my_cond.wait(guard),那么就跟第二个参数lanbda表达式返回false效果一样;
            //!wait()将解锁互斥量,并堵塞到本行,堵塞到其他某个线程调用notify_one()成员函数为止;

            //!当其他线程用notify_one()将本wait(原来是堵塞状态)唤醒后,wait()开始工作:
            //!(a)wait()不断地尝试重新获取互斥量锁my_mutex1,如果获取不到,那么线程就会卡在wait()这里等待获取,如果获取到了,wait()就继续执行(b);
            //!(b)
            //! b.1)如果wait有第二个参数(这里是lambda表达式),判断这个lambda表达式(或其他函数),如果表达式为false,那么wait又对互斥量解锁,然后有休眠等待再次被notify_one()唤醒;
            //! b.2)如果lambda表达式为true,则wait()返回,程序向下执行(此时互斥量锁被锁着);
            //! b.3)如果wait()没有第二个参数,则wait()返回,程序向下执行;

            my_cond.wait(guard, [this] { //lambda表达式就是一个可调用对象
                if (!msgRecvQueue.empty())
                    return true;
                return false;
            });

            //程序执行在这个地方,此时互斥锁是锁着的
            command = msgRecvQueue.front(); //返回第一个元素
            msgRecvQueue.pop_front();
            guard.unlock();
            cout << "outMsgRecvQueue()取出一个元素:" << command << endl;
        }
    }

private:
    list<int> msgRecvQueue;
    std::mutex my_mutex1;
    std::condition_variable my_cond;
};
int main()
{
    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();
    return 0;
}
//上面两个线程条件唤醒并不是一对一的,即并不是执行一次my_cond.notify_one(),等待线程不一定能被执行,即使等待线程能够被唤醒,但是两个线程依然需要竞争my_mutex1,这时候,可能唤醒线程先拿到锁,此时被唤醒的线程依然需要重新需要被my_cond.notify_one()重新唤醒。

15.2 notify_all()

假如现在不止一个线程在等待,而是多个线程在等待,则此时若想唤醒所有的线程,则采用notify_one()显然是不合适的,因为notify_one()只能唤醒一个线程。此时需要采用notify_all()唤醒所有线程;

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable> //! for condition_variable
#include <list>
#include <string>
using namespace std;
class A
{
public:
    //! 移动所有权方式2
    std::unique_lock<std::mutex> rtn_unique_lock()
    {
        std::unique_lock<std::mutex> tempguard(my_mutex1);
        return tempguard; //返回这种局部对象tempguard会导致系统生成临时unique_lock对象,并调用unique_lock的移动构造函数;
    }
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100000; ++i)
        {
            cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
            std::unique_lock<std::mutex> guard1 = rtn_unique_lock();

            msgRecvQueue.push_back(i); 

            // //! 唤醒一个等待线程!
            // //! 现在主函数里面定义了两个等待线程,myOutMsgObj与myOutMsgObj2,此时若采用notify_one(),那么唤醒
            // //! 线程是随机的,这是由于notify_one()一次只能唤醒一个线程;
            // my_cond.notify_one();   

            //!唤醒所有等待线程!
            //!现在主函数里面定义了两个等待线程,myOutMsgObj与myOutMsgObj2,两个线程具备唤醒,但是两个线程会同时向下
            //!执行吗?答案是否定的,因为两个线程唤醒后都需要对互斥量my_mutex1进行加锁,而锁只有一把,所以只能有一个会成功
            //!加锁,假如此时线程myOutMsgObj成功加锁,那么该线程会继续向下执行,myOutMsgObj2线程就会继续等待my_mutex1;
            my_cond.notify_all();                             
        }
    }
   
    void outMsgRecvQueue()
    {
        int command = 0;
        while (true)
        {
            std::unique_lock<std::mutex> guard(my_mutex1);
            my_cond.wait(guard, [this] { //lambda表达式就是一个可调用对象
                if (!msgRecvQueue.empty())
                    return true;
                return false;
            });

            //程序执行在这个地方,此时互斥锁是锁着的
            command = msgRecvQueue.front(); //返回第一个元素
            msgRecvQueue.pop_front();
            guard.unlock();
            cout << "outMsgRecvQueue()取出一个元素:" << command << endl;
        }
    }

private:
    list<int> msgRecvQueue;
    std::mutex my_mutex1;
    std::condition_variable my_cond;
};
int main()
{
    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
    std::thread myOutMsgObj2(&A::outMsgRecvQueue, &myobja);
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myOutMsgObj2.join();
    myInMsgObj.join();
    return 0;
}

16. std::async 、std::future 创建后台任务并返回值

16.1 普通函数作为async()入口函数

希望通过线程返回一个结果;
std::async是一个函数模板,用来启动一个异步任务(就是自动创建一个线程并开始执行对应的线程的入口函数,他返回一个std::future对象),启动起来的一个异步任务之后,它返回一个std::future对象,std::future是一个类模板;
std::future对象里面就含有线程入口函数所返回的结果(线程返回的结果),我们可以通过调用future对象的成员函数get()来获取结果,如果此时对应的子线程没有执行完毕,则会等待子线程执行完毕,get()之后不能重复get()

#include <iostream>
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <future>
using namespace std;
int mythread(int num)
{
    int test=num;
    cout<<"mythread() start"<<" threadid ="<<std::this_thread::get_id()<<endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(5000)); //5s
    cout<<"mythread() end"<<" threadid ="<<std::this_thread::get_id()<<endl;
    return test;
}
int main()
{
    int num=127;
    cout<<"main() start"<<" threadid ="<<std::this_thread::get_id()<<endl;
    std::future<int> result=std::async(mythread,num);//创建一个线程并开始执行
    cout<<"continue...!"<<endl;
    int def=0;
    //result.wait();//等待子线程结束,没有返回值;
    cout<<result.get()<<endl;//等待子线程执行完,才能拿到结果!
    
    cout<<"main done!"<<endl;
    return 0;
}

16.2 采用成员函数作为async()入口函数:

#include <iostream>
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <future>
using namespace std;
class A
{
public:
    int mythread(int num)
    {
        cout << "mythread() start"
             << " threadid =" << std::this_thread::get_id() << endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(5000)); //5s
        cout << "mythread() end"
             << " threadid =" << std::this_thread::get_id() << endl;
        return num;
    }
};

int main()
{
    int num = 127;
    A myobja;

    cout << "main() start"
         << " threadid =" << std::this_thread::get_id() << endl;

    //!成员函数作为入口函数
    std::future<int> result = std::async(&A::mythread,&myobja,num);//第二个是对象引用,才能保证是同一个对象;

    cout << "continue...!" << endl;
    int def = 0;
    //result.wait();//等待子线程结束,没有返回值;
    cout << result.get() << endl; //等待子线程执行完,才能拿到结果!

    cout << "main done!" << endl;
    return 0;
}

16.3 std::async()第二参数:

async()接受std::launch类型的参数(枚举类型),对async()类型进行设置:

  1. std::launch::async:在调用async()函数时,开始创建线程,并开始从入口函数执行.
  2. std::launch::deferred:表示线程入口函数被延迟std::futurewait()get()成员函数调用时才执行,若std::futurewait()get()成员函数没有被调用,则不会执行入口函数采用该种方式不会创建子线程
  3. std::launch::async|std::launch::deferred (默认值)系统二选一,具有不确定性
  4. 不带第二参数,只传递入口函数,默认第二参数为:std::launch::async|std::launch::deferred
#include <iostream>
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <future>
using namespace std;
class A
{
public:
    int mythread(int num)
    {
        cout << "mythread() start"
             << " threadid =" << std::this_thread::get_id() << endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(5000)); //5s
        cout << "mythread() end"
             << " threadid =" << std::this_thread::get_id() << endl;
        return num;
    }
};

int main()
{
    int num = 127;
    A myobja;

    cout << "main() start"
         << " threadid =" << std::this_thread::get_id() << endl;

    //!成员函数作为入口函数
    std::future<int> result = std::async(std::launch::async,&A::mythread,&myobja,num); //创建一个线程并开始执行
    cout << "continue...!" << endl;
    
    int def = 0;
    //result.wait();//等待子线程结束,没有返回值;
    
    cout << result.get() << endl; //等待子线程执行完,才能拿到结果!
    cout << "main done!" << endl;
    return 0;
}

16.4 std::async()深入

std::async()有两个第二参数:

  1. std::launch::deferred:延迟调用;并且创建新线程,延迟future对象调用.get().wait()函数是才执行mythread(),如果没有调用,就不会执行入口函数mythread()
  2. std::launch::async:强制创建一个线程,强制这个任务在新线程上执行,
  3. std::launch::async|std::launch::deferred (默认值)系统二选一,具有不确定性
  4. 不带第二参数,只传递入口函数,默认第二参数为:std::launch::async|std::launch::deferred
    std::async()与std::thread()的区别:
  5. std::thread()
    • std::thread()创建线程,如果系统资源紧张,创建线程失败,这个程序就会崩溃;
    • std::thread()创建线程,如果有返回值,可以通过引用,全局量或者指针的形式获取。
  6. std::async() 创建异步任务,可能创建线程,也可能不创建线程。并且可以通过future较为容易的拿到返回值;
  7. 如果系统资源有限时:
    • std::thread()创建的线程 太多时,则可能创建线程失败,系统报告异常。
    • std::async(),一般不会报异常崩溃,因为系统资源紧张导致无法创建新线程的时候,std::async()这种不带第二参数的调用,就不会创建新线程,而是后续谁调用了.get()来请求结果,那么这个异步任务就运行在这条get()语句所在的线程上。如果使用std::launch::async强制创建新的线程,系统资源紧张也是会导致奔溃。所以一个程序中创建的新线程,不宜超过100-200个;

16.5 std::packaged_task:打包任务:

是一个类模板,它的模板参数是各种可调用对象,;通过std::packaged_task 来把线程中可调用的结果包装起来,方便将来作为线程入口函数来调用;

#include <iostream>
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <future>
using namespace std;
int mythread(int num)
    {
        cout << "mythread() start"
             << " threadid =" << std::this_thread::get_id() << endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(5000)); //5s
        cout << "mythread() end"
             << " threadid =" << std::this_thread::get_id() << endl;
        return num;
    }
int main()
{
    int num = 127;
    
    cout << "main() start"
         << " threadid =" << std::this_thread::get_id() << endl;

    std::packaged_task<int(int)>mypt(mythread); //把函数mythread通过packaged_task包装起来

    std::thread t1(std::ref(mypt),num); //线程直接开始执行,第二个参数作为线程入口函数的参数;
    cout<<"jion()调用..."<<endl;
    t1.join();   //等待线程结束;
    cout<<"jion()调用结束"<<endl;    
    std::future<int> result=mypt.get_future();//std::future对象中包含线程入库函数的返回结果,
                                                //这里result保存mythread返回的对象;
   
    //! 与调用std::async(std::launch::async,&A::mythread,&myobja,num);相比,子线程会在
    //! t1.jion();结束时返回结果,所以result.get()不用在等待线程结束。
    cout<<result.get()<<endl;
    cout << "main done!" << endl;
    return 0;
}

联合lambda表达式:

td::packaged_task<int(int)>mypt([](int num){
         cout << "mythread() start"
             << " threadid =" << std::this_thread::get_id() << endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(5000)); //5s
        cout << "mythread() end"
             << " threadid =" << std::this_thread::get_id() << endl;
        return num;

    }); //把函数mythread通过packaged_task包装起来
    mypt(125);
    std::future<int> result=mypt.get_future();//std::future对象中包含线程入库函数的返回结果,
                                                //这里result保存mythread返回的对象;
   
    cout<<result.get()<<endl;
    cout << "main done!" << endl;
    return 0;

16.6 std::promise,类模板

作用同std::async()相似用于在多线程中返回结果;

#include <iostream>
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <future>
using namespace std;

void mythread(std::promise<int> &tmpp,int calc) //第一个参数用于输出;
{
    //做一些操作:
    calc++;
    calc *=10;

    cout << "mythread"
         << " threadid =" << std::this_thread::get_id() << endl;
    //....
    std::this_thread::sleep_for(std::chrono::milliseconds(5000));

    //...出结果
    int result=calc; //保存结果
    tmpp.set_value(result);
}
void mythread2(std::future<int> &tmpf)
{
    auto result=tmpf.get();
    cout<<"mythread2 result "<<result<<'\t'<< std::this_thread::get_id() << endl;
    return;
}
int main()
{
    int num = 127;
    cout << "main() start"
         << " threadid =" << std::this_thread::get_id() << endl;

    std::promise<int> myprom; //声明一个std::promise对象myprom,保存类型为int;

    std::thread t1(mythread,std::ref(myprom),num);
    t1.join();

    // //!获取结果值
    std::future<int> ful=myprom.get_future(); //promise和future绑定,用于获取线程返回值;
    // auto result=ful.get();
    // cout<<"result = "<<result<<endl;

    //将值传递给第二个线程
    std::thread t2(mythread2,std::ref(ful));
    t2.join();
    cout << "main done!" << endl;
    return 0;
}

17.std::future 的其他成员函数:

17.1 wait_for()

wait_for():返回当前执行的状态;

#include <iostream>
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <future>
using namespace std;
int mythread(int num)
{
    int test=num;
    cout<<"mythread() start"<<" threadid ="<<std::this_thread::get_id()<<endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(5000)); //5s
    cout<<"mythread() end"<<" threadid ="<<std::this_thread::get_id()<<endl;
    return test;
}
int main()
{
    int num=127;
    cout<<"main() start"<<" threadid ="<<std::this_thread::get_id()<<endl;
    //std::future<int> result=std::async(mythread,num);
    std::future<int> result=std::async(std::launch::deferred,mythread,num);//std::async()使用std::launch::deferred参数,std::future_status::deferred成立;
    cout<<"continue...!"<<endl;
    
    //!wait_for
    std::future_status status=result.wait_for(std::chrono::seconds()); //等待1s
    if(status==std::future_status::timeout) //超时,我想等你1s,希望你返回,你没有返回,
                                                //那么status==std::future_status::timeout成立
    {
        //表示线程没有执行完毕
        cout<<"超时!线程还没有执行完毕"<<endl;
    }
    else if(status==std::future_status::ready)
    {
        cout<<"线程执行完毕,返回"<<endl;
        cout<<result.get()<<endl;
    }
    else if(status==std::future_status::deferred)
    {
        cout<<"函数延迟执行!"<<endl;
        cout<<result.get()<<endl;
    }
    cout<<"main done!"<<endl;
    return 0;
}

17.2 std::shared::future

std::future 中获取的数据只能调用一次get(),这是由于get()函数在设计时,采用移动语义。相当调用了std::move();
std::shared_future :也是一个类模板,不过get()函数是复制数据,所以可以多次get(),获取数据;

#include <iostream>
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <future>
using namespace std;
int mythread(int num)
{
    cout << "mythread() start"
         << " threadid =" << std::this_thread::get_id() << endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(5000)); //5s
    cout << "mythread() end"
         << " threadid =" << std::this_thread::get_id() << endl;
    return num;
}
void mythread2(std::shared_future<int> &tmpf)
{
    auto result=tmpf.get();
    cout<<"mythread2 result "<<result<<'\t'<< std::this_thread::get_id() << endl;
    return;
}
int main()
{
    int num = 127;

    cout << "main() start"
         << " threadid =" << std::this_thread::get_id() << endl;

    std::packaged_task<int(int)> mypt(mythread);

    std::thread t1(std::ref(mypt), num);
    cout << "jion()调用..." << endl;
    t1.join(); //等待线程结束;
    cout << "jion()调用结束" << endl;
    std::future<int> result = mypt.get_future();

    bool ifcanget = result.valid();
    //!用result初始化result_s
    //! a)   std::shared_future<int> result_s(std::move(result));
    std::shared_future<int> result_s(result.share()); //执行完毕后,result_s有值,result空了;
    ifcanget=result.valid();
    auto mythreadresult=result_s.get();
    mythreadresult=result_s.get(); //多次调用

    //将值传递给第二个线程
    std::thread t2(mythread2,std::ref(result_s));
    t2.join();
    return 0;
}

18. 原子操作 std::atomic

18.1 引例

#include <iostream>
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <future>
using namespace std;
std::mutex my_mutex;
int g_mycount=0;
void mythread()
{
    for(int i=0;i<1000000;i++)
    {
        // my_mutex.lock();
        g_mycount++;
        // my_mutex.unlock();
    }
    return ;
}
int main()
{
    std::thread t1(mythread);
    std::thread t2(mythread);
    t1.join();
    t2.join();
    cout<<"m_mycount="<<g_mycount<<endl;
    return 0;
}

没有数据保护的情况下,两个线程同时对g_mycount进行计算,由于线程之间的上下文切换,常常会导致计算结果出错(!=2000000)。这是由于简单的加法计算,转换成低级语言时,并不是一步到位的,而是经过多步完成,假如现在存在两个线程A与线程B,当线程A正在进行加法过程中,还没有得到正确结果,此时发生了上下文切换,从而导致此次加法出错,以此类推,最终导致最后的结果出错。

18.2 原子操作

原子操作:是在多线程中不会被打断的程序执行片段;原子操作,比互斥量效率上更胜一筹;
互斥量的加锁一般是针对一个代码段(几行代码),而原子操作针对的一般都是一个变量,而不是一个代码段;
C++11 提供了一个原子类型 std::atomic<T>,通过这个原子类型管理的内部变量就可以称之为原子变量,我们可以给原子类型指定任意的类型作为模板参数,因此原子变量也可以是任意的类型。

C++11 内置了整形的原子变量,这样就可以更方便的使用原子变量了。在多线程操作中,使用原子变量之后就不需要再使用互斥量来保护该变量了,用起来更简洁**。因为对原子变量进行的操作只能是一个原子操作(atomic operation),原子操作指的是不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何的上下文切换**。多线程同时访问共享资源造成数据混乱的原因就是因为 CPU 的上下文切换导致的,使用原子变量解决了这个问题,因此互斥锁的使用也就不再需要了。

#include <iostream>
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <future>
#include <atomic>
using namespace std;
std::mutex my_mutex;

//int g_mycount=0;

//!原子操作
std::atomic<int> g_mycount;

std::atomic<bool> g_ifend;//线程退出标记,这里是原子操作,防止写的时候出错

void mythread()
{
    for(int i=0;i<1000000;i++)
    {
        // my_mutex.lock();
        //g_mycount++;
        // my_mutex.unlock();
        g_mycount++; //对应的操作是原子操作,执行过程不会被打断。
    }
    return ;
}
void mythread2()
{
    cout<<boolalpha<<g_ifend<<endl;
    while (g_ifend==false)
    {
        //系统没要求线程退出,所以本线程可以干自己的事情
        cout<<"thread id "<<std::this_thread::get_id()<<"运行中..."<<endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    }
    cout<<"thread id "<<std::this_thread::get_id()<<"运行结束"<<endl;
}
int main()
{
    // std::thread t1(mythread);
    // std::thread t2(mythread);
    // t1.join();
    // t2.join();
    // cout<<"m_mycount="<<g_mycount<<endl;
    std::thread t1(mythread2);
    std::thread t2(mythread2);
    std::this_thread::sleep_for(std::chrono::milliseconds(5000));
    g_ifend=true;
    t1.join();
    t2.join();
    cout<<"运行结束!"<<endl;
    return 0;
}

可以用于计数或者统计(累计发出了多少个数据包,累计接受到了多少数据包)

18.3 原子操作深入

一般atomic原子操作,针对++,--,+=,&=,|=,^=是有效的,其他操作可能导致错误,如下:

#include <iostream>
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <future>
#include <atomic>
using namespace std;
std::mutex my_mutex;

//int g_mycount=0;

//!原子操作
std::atomic<int> g_mycount;
void mythread()
{
    for(int i=0;i<1000000;i++)
    {
        //!合法
        // g_mycount++;
        // g_mycount+=1;

        //!非法
        g_mycount=g_mycount+1;
    }
    return ;
}
int main()
{
    std::thread t1(mythread);
    std::thread t2(mythread);
    t1.join();
    t2.join();
    cout<<"m_mycount="<<g_mycount<<endl;
    
    cout<<"运行结束!"<<endl;
    return 0;
}

19. Windows临界区与std::recursive_mutex(递归的独占互斥量)

19.1 Windows临界区

Windows临界区的作用与c++互斥量的作用是相似的,但是只适合在windows平台使用;
在"同一个线程中(不同线程会卡住等待)",Windows临界区变量代表临界区的进入(EnterCriticalSection(m_pCritical);)可以被多次调用,但是需要注意的是,调用了几次(EnterCriticalSection(m_pCritical);)就需要调用几次(LeaveCriticalSection(m_pCritical);)。而在c++11中,不允许“同一个线程”中lock()同一个互斥量多次,否则异常。如果确实需要在同意线程中对一个互斥量进行多次调用lock(),需要使用std::recursive_mutex(递归的独占互斥量)

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

#include <Windows.h>

using namespace std;
#define _WINDOWSJQ_

//本类用于自动释放临界区,放置忘记调用LeaveCriticalSection(m_pCritical);导致死锁的发生。作用同std::lock_guard()类似;
class CWinLock
{
public:
    CWinLock(CRITICAL_SECTION *pCritical)
    {
        m_pCritical = pCritical;
        EnterCriticalSection(m_pCritical);
    }
    ~CWinLock()
    {
        LeaveCriticalSection(m_pCritical);
    }

private:
    CRITICAL_SECTION *m_pCritical;
};
class A
{
public:
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100000; ++i)
        {
            cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
#ifdef _WINDOWSJQ_
            //EnterCriticalSection(&my_winsec);//进入临界区(加锁)
            //msgRecvQueue.push_back(i);
            //LeaveCriticalSection(&my_winsec);//退出临界区(解锁)

            //使用CWinLock,相当于std::lock_guard()
            CWinLock lock(&my_winsec);
            msgRecvQueue.push_back(i);
#else

            // _WINDOWSJQ_

            //写数据时进行保护
            my_mutex.lock();
            msgRecvQueue.push_back(i); //保证在插入数据时,锁住程序,不被修改。
            //写完后,进行解锁
            my_mutex.unlock();
#endif
        }
    }
    bool outMsgLULProc(int &command)
    {
#ifdef _WINDOWSJQ_

        EnterCriticalSection(&my_winsec);
        if (!msgRecvQueue.empty())
        {
            command = msgRecvQueue.front(); //返回第一个元素
            msgRecvQueue.pop_front();       //移除第一个元素
            LeaveCriticalSection(&my_winsec);
            return true;
        }
        LeaveCriticalSection(&my_winsec);
#else
        my_mutex.lock();
        if (!msgRecvQueue.empty())
        {
            command = msgRecvQueue.front(); //返回第一个元素
            msgRecvQueue.pop_front();       //移除第一个元素
            my_mutex.unlock();
            return true;
        }
        my_mutex.unlock();
#endif // _WINDOWSJQ_
        return false;
    }
    void outMsgRecvQueue()
    {
        int command = 0;
        for (int i = 0; i < 10000; ++i)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
            }
            else
            {
                cout << "outMsgRecvQueue()为空。" << endl;
            }
        }

        cout << "finished!" << endl;
    }
    A()
    {
#ifdef _WINDOWSJQ_
        InitializeCriticalSection(&my_winsec); //使用临界区必须要进行初始化
#endif                                         //  _WINDOWSJQ_
    }

private:
    list<int> msgRecvQueue;
    std::mutex my_mutex;
#ifdef _WINDOWSJQ_
    CRITICAL_SECTION my_winsec; //windows中的临界区,与c++中mutex非常类似
#endif
};
int main()
{
    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();
    return 0;
}

19.2 std::recursive_mutex(递归的独占互斥量)

std::mutex:独占互斥量,自己lock时,别人不能lock;
std::recursive_mutex:递归的独占互斥量:允许同一个线程,同一个互斥量多次被lock();
std::recursive_mutex与std::mutex相比,效率上更低一点。

 //std::mutex my_mutex1; 
 std::recursive_mutex;//尽量少使用recursive_mutex;

20.带超时的互斥量std::timed_mutex 和 std::recursive_timed_mutex

std::timed_mutex:带超时的互斥量独占锁:**防止互斥量一直在等待!**比如一个线程正在长时间占用锁,若采用互斥量独占互斥量,那么想要拿到锁,需要等待其他线程执行完毕,采用带超时的互斥量的独占锁,可以避免这一点。与lock_guard()中的trylock()相似;

  • try_lock_for():参数是一个一段时间,等待一段时间。如果在这段时间中拿到了锁,就会继续执行,如果超时这段时间没有拿到锁,就会执行其他语句比如(if …else…)。
  • try_lock_until():参数是一个未来时间点,在这个时间点之前,如果拿到了锁,那么就会走下下来,如果时间没有到,也会走下来
    std::recursive_timed_mutex:递归带超时的互斥量独占锁
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class A
{
public:
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100000; ++i)
        {
            cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
            std::chrono::milliseconds timeout(100);

            //!try_lock_for()
            //if (my_mutex.try_lock_for(timeout)) //!等待100ms来尝试获取锁

            //!try_lock_for()
            if(my_mutex.try_lock_until(chrono::steady_clock::now()+timeout))//!等待100ms来尝试获取锁
            {
                //拿到锁
                msgRecvQueue.push_back(i); //保证在插入数据时,锁住程序,不被修改。
                                           
                my_mutex.unlock();//!写完后,进行解锁
            }
            else
            {
                //没拿到锁
                std::this_thread::sleep_for(200ms);
                cout<<"此次没有拿到锁,等待200ms"<<endl;
            }
        }
    }
    bool outMsgLULProc(int &command)
    {
        my_mutex.lock();
        if (!msgRecvQueue.empty())
        {
            command = msgRecvQueue.front(); //返回第一个元素
            msgRecvQueue.pop_front();       //移除第一个元素
            my_mutex.unlock();
            return true;
        }
        my_mutex.unlock();
        return false;
    }
    void outMsgRecvQueue()
    {
        int command = 0;
        for (int i = 0; i < 10000; ++i)
        {
            bool result = outMsgLULProc(command);
            if (result == true)
            {
                cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
            }
            else
            {
                cout << "outMsgRecvQueue()为空。" << endl;
            }
        }

        cout << "finished!" << endl;
    }

private:
    list<int> msgRecvQueue;
    //std::mutex my_mutex;
    std::timed_mutex my_mutex; //带超时功能的互斥量
};
int main()
{
    A myobja;
    std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
    std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

    myOutMsgObj.join();
    myInMsgObj.join();
    return 0;
}

21.线程池

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值