c++多线程(一)

c++ 11 多线程

主要参考https://www.bilibili.com/video/BV1Yb411L7ak?p=2
在这里插入图片描述

并发、进程、线程的基本概念和综述

并发,线程,进程要求必须掌握
1.1)并发
两个或者更多的任务(独立的活动)同时发生(进行),一个程序同时执行多个独立的任务
计算机,单核cpu(中央处理器):某一个时刻能执行一个任务(进程):由操作系统调度,每秒钟进行多次所谓的“任务切换,这是并发的假象(不是真正的并发);这种切换上下文切换)是要有时间开销的,比如操作系统要保存你切换时的各种状态,执行进度等信息,都需要时间,一会切换回来的时候要复原这些信息。
硬件发展,出现了多处理器计算机:用于服务器和高性能计算领域。
在一块芯片上有多核(双核,4核,8核,10核
/能够实现真正的并行执行多个任务(硬件并发)
//使用并发的原因:主要就是同时可以干多个事,提高性能;
我的电脑上现在就有176个进程,但是显然没有176个核。
在这里插入图片描述

所以需要切换。
在这里插入图片描述

(1.2)可执行程序
磁盘上的一个文件, windows下,一个扩展名为。exe的。linux,1s-1a, rwxrwxrwx(x执行权限);
(1.3)进程:大家已经知道了可执行程序是能够运行。
/ windows下,双击一个可执行程序来运行。linux下,/文件名。
进程,就是一个可执行程序运行起来了,就叫创建了一个进程
进程,就是运行起来了的可执行程序;
(1.4)线程
a)每个进程(执行起来的可执行程序),都有一个主线程,这个主线程是唯一的,也就是一个进程中只能有一个主线程。
b)当你执行一个执行程序,产生了一个进程后,这个主线程就随着这个进程黑默的启动起来了
这个程序的时候,实际上是进程的主线程来执行(调用)这个main函数中的代码
主线程与进程唇齿相依,有你必然有我,有我必然有你,没有我必然没有你
线程:用来执行代码的
/线程这个东西理解成一条代码的执行通路(道路)在这里插入图片描述

除了主线程之外,我们可以通过自己写代码来创建其他线程,其他线程走的是别的道路,甚至去不同的地方
我每创建一个新线程,我就可以在同一个时刻,多做不同的事(多走一条不同的代码执行路径)
多线程(并发
线程并不是越多越好,每个线程,都需要一个独立的堆栈空间(1M),线程之间的切换要保存很多中间状态,切换会耗费本该属于程序运行的时间
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

多线程函数

在这里插入图片描述
在这里插入图片描述
程序运行起来,生成一个进程,该进程所属的主线程开始自动运行;当主线程从main()函数返回,则整个进程执行完毕
主线程从main()开始执行,那么我们自己创建的线程,也需要从一个函数开始运行(初始函数),一旦这个函数运行完毕,线程也结束运行
整个进程是否执行完毕的标志是:主线程是否执行完,如果主线程执行完毕了,就代表整个进程执行完毕了,此时如果其他子线程还没有执行完,也会被强行终止【此条有例外,以后会解释】
创建一个线程:

包含头文件thread
写初始函数
在main中创建thread
必须要明白:有两个线程在跑,相当于整个程序中有两条线在同时走,即使一条被阻塞,另一条也能运行
在这里插入图片描述
在这里插入图片描述
sublime+mingw出问题:
https://blog.csdn.net/qq_34719188/article/details/84193649

#include <iostream>
#include <thread>
using namespace std;

void myPrint()
{
    cout << "myPrint start" << endl;
    //-------------
    //-------------
    cout << "myPrint end" << endl;
    return;
}

int main()
{
    cout<<"main"<<endl;
    thread p(myPrint);
    p.join();
    cout << "Hello World!" << endl;
    return 0;
}

在这里插入图片描述

#include <iostream>
#include <thread>
using namespace std;

void myPrint()
{
    cout << "myPrint start" << endl;
    //-------------
    //-------------
    cout << "myPrint end" << endl;
    return;
}

int main()
{
    cout<<"main"<<endl;
    thread p(myPrint);
    // p.join();
    cout << "Hello World!" << endl;
    return 0;
}

没有join的阻塞,顺序就乱了,也不是正常的结束。
在这里插入图片描述
这是因为主线程main执行完了,但是线程myPrint还未执行完。最后虽然myPrint也执行结束了,但是那是因为发生了异常在这里插入图片描述

#include <iostream>
#include <thread>
using namespace std;

void myPrint()
{
    cout << "myPrint start" << endl;
    //-------------
    //-------------
    cout << "myPrint end" << endl;
    cout<<"1"<<endl;
    cout<<"2"<<endl;
    cout<<"3"<<endl;
    return;
}

int main()
{
    cout<<"main"<<endl;
    thread p(myPrint);
    // p.join();
    p.detach();
    cout << "Hello World!" << endl;
    return 0;
}

在这里插入图片描述
我们发现子线程似乎没有运行。
同样的代码再执行一次:在这里插入图片描述

#include <iostream>
#include <thread>
using namespace std;

void myPrint()
{
    cout << "myPrint start" << endl;
    //-------------
    //-------------
    cout << "myPrint end" << endl;
    cout<<"1"<<endl;
    cout<<"2"<<endl;
    cout<<"3"<<endl;
    return;
}

int main()
{
    cout<<"main"<<endl;
    thread p(myPrint);
    // p.join();

    cout << "Hello World!" << endl;
     p.detach();
    return 0;
}

只有这种结果。
在这里插入图片描述

#include <iostream>
#include <thread>
using namespace std;

void myPrint()
{
    cout << "myPrint start" << endl;
    //-------------
    //-------------
    cout << "myPrint end" << endl;
    cout<<"1"<<endl;
    cout<<"2"<<endl;
    cout<<"3"<<endl;
    return;
}

int main()
{
    thread p(myPrint);
        p.detach();
    cout<<"main"<<endl;

    // p.join();

    cout << "Hello World!" << endl;

    return 0;
}

在这里插入图片描述
在这里插入图片描述

#include <iostream>
#include <thread>
using namespace std;

void myPrint()
{
    cout << "myPrint start" << endl;
    //-------------
    //-------------
    cout << "myPrint end" << endl;
    cout<<"1"<<endl;
    cout<<"2"<<endl;
    cout<<"3"<<endl;
    cout<<"4"<<endl;
    cout<<"5"<<endl;
    return;
}

int main()
{
    thread p(myPrint);
    p.detach();
    cout<<"main"<<endl;

    // p.join();

    cout << "Hello World!" << endl;

    return 0;
}

在这里插入图片描述
这个结果其实也不确定,这一种是5没打印出来。这是因为主线程的退出导致进程的退出,这中间有段时间,而进程一旦退出,这个打印的窗口就没了,其实子线程也打印了,但是这个5我们看不到了而已。

#include <iostream>
#include <thread>
using namespace std;

void myPrint()
{
    cout << "myPrint start" << endl;
    //-------------
    //-------------
    cout << "myPrint end" << endl;
    cout<<"1"<<endl;
    cout<<"2"<<endl;
    cout<<"3"<<endl;
    cout<<"4"<<endl;
    cout<<"5"<<endl;
    return;
}

int main()
{
    thread p(myPrint);
    p.detach();
    cout<<"main"<<endl;
    p.join();

    // p.join();

    cout << "Hello World!" << endl;

    return 0;
}

在这里插入图片描述
先detach后join会有异常。反之一样。不过异常发生的地方不同,先detach再join应该是执行到主线程的p.join()出错的,而先join()在detach是在阻塞执行完myPrint后,转而执行p.detach()时报错的。在这里插入图片描述

joinable()判断子线程是否可以join和detach。
线程类参数是一个可调用对象。
一组可执行的语句称为可调用对象,c++中的可调用对象可以是函数、函数指针、lambda表达式、bind创建的对象或者重载了函数调用运算符的类对象。
在这里插入图片描述
①创建一个类,并编写圆括号重载函数,初始化一个该类的对象,把该对象作为线程入口地址:

#include <iostream>
#include <thread>
using namespace std;
class Ta
{
    int &n;
public:
    Ta(int &m):n(m){cout<<"构造函数"<<endl;}
    Ta( const Ta  & ta):n(ta.n)
    {cout<<"复制构造函数"<<endl;

    }
    ~Ta()
    {
        cout<<"析构函数"<<endl;
    }
    void operator()()
    {
        cout << "我的线程开始运行" << endl;

        //-------------
        //-------------
        cout << "我的线程运行完毕" << endl;
    }
};

int main(int argc, char const *argv[])
{
    //main函数里的:
    cout<<"mainstart"<<endl;
    int a=1;
    Ta ta(a);
    cout<<"进入子线程"<<endl;
    thread myThread(ta);
    myThread.join();
    cout<<"mainend"<<endl;
    return 0;
}


在这里插入图片描述
这个ta在子线程里复制了两次我没想到。
这个程序有问题,在于类里有引用。如果用了detach,那么主线程可能结束了,a的资源被释放而子线程还有a的引用,这会导致问题。

在这里插入图片描述
类函数

#include <iostream>
#include <thread>
using namespace std;
class Data_
{
public:
    Data_(const Data_& d)
    {
        cout<<"复制构造"<<endl;
    }
    Data_()
    {
        cout<<"构造"<<endl;
    }
    ~Data_()
    {
        cout<<"析构"<<endl;
    }
    void operator()()
    {
        cout<<"调用"<<endl;
    }
    void GetMsg(){
        cout<<"msg"<<endl;
    }
    void SaveMsh(){
        cout<<"save"<<endl;
    }
};
//main函数里



int main(int argc, char const *argv[])
{
    //main函数里的:
    cout<<"mainstart"<<endl;
   Data_ s;
    //第一个&意思是取址,第二个&意思是引用,相当于std::ref(s)
    //thread oneobj(&Data_::SaveMsh,s)传值也是可以的
    //在其他的构造函数中&obj是不会代表引用的,会被当成取地址
    thread oneobj(&Data_::SaveMsh,&s);
    thread twoobj(&Data_::GetMsg,&s);
    oneobj.join();
    twoobj.join();
    cout<<"mainend"<<endl;
    return 0;
}

在这里插入图片描述

如果传值:在这里插入图片描述
两个子线程的执行互不干扰。
在这里插入图片描述
完成的先后取决于启动线程的先后。
在这里插入图片描述
这个复制构造函数按照视频里老师讲的应该是出现一次,不过他用的是MSVC,我是G++,不同的编译器处理可能不同。

线程传参

在这里插入图片描述

#include <iostream>
#include <thread>
using namespace std;

void myPrint(const int &i, char* pmybuf)
{
    //如果线程从主线程detach了
    //i不是mvar真正的引用,实际上值传递,即使主线程运行完毕了,子线程用i仍然是安全的,但仍不推荐传递引用
    //推荐改为const int i
    cout << i << endl<<&i<<endl;
    //pmybuf还是指向原来的字符串,所以这么写是不安全的
    cout << pmybuf << endl<<&pmybuf<<endl;
}

int main()
{
    int mvar = 1;
    int& mvary = mvar;
    cout<<&mvar<<endl<<&mvary<<endl;
    char mybuf[] = "this is a test";
    cout<<"mybuf"<<&mybuf<<endl;
    thread myThread(myPrint, mvar, mybuf);//第一个参数是函数名,后两个参数是函数的参数
    myThread.join();
    //myThread.detach();

    cout << "Hello World!" << endl;
}

在这里插入图片描述
看来g++的处理很谨慎,无论是引用传递还是指针传递,都会复制一份。
这样是很好的,因为即便是detach了,也不出现问题。

#include <iostream>
#include <thread>
using namespace std;
class A
{
public:
    int a;

    A(const A &b):a(b.a)
    {
        cout<<"复制构造"<<endl;
    }
    A( int n):a(n)
    {
        cout<<"构造"<<endl;
    }
    ~A()
    {
        cout<<"析构"<<endl;
    }

};
void myprint(const A & a)
{
    cout<<"start";
    cout<<&a<<endl;
}

int main()
{
    int mvar = 1;
    thread p(myprint,mvar);
    p.join();

    cout << "Hello World!" << endl;
}

如果有隐式的类型转换。在这里插入图片描述

如果用了detach:
在这里插入图片描述
可能这个隐式的转换会来不及,这个myvar的空间就释放了。在这里插入图片描述
显式的好一些。
总结

如果传递int这种简单类型,推荐使用值传递,不要用引用
如果传递类对象,避免使用隐式类型转换,全部都是创建线程这一行就创建出临时对象,然后在函数参数里,用引用来接,否则还会创建出一个对象

终极结论:建议不使用detach

#include <iostream>
#include <thread>
using namespace std;
class A
{
public:
    int a;

    A(const A &b):a(b.a)
    {
        cout<<"复制构造"<<endl;
    }
    A( int n):a(n)
    {
        cout<<"构造"<<endl;
    }
    ~A()
    {
        cout<<"析构"<<endl;
    }

};
void myprint(const A  a)
{
    cout<<"start";
    cout<<&a<<endl;
}

int main()
{
    int mvar = 1;
    thread p(myprint,A(mvar));
    p.join();

    cout << "Hello World!" << endl;
}

在这里插入图片描述
函数不用引用接受的话,g++要三次复制构造,这…

#include <iostream>
#include <thread>
using namespace std;
class A
{
public:
    int a;

    A(const A &b):a(b.a)
    {
        cout<<"复制构造"<<endl;
         cout<<this<<" "<<this_thread::get_id()<<endl;
    }
    A( int n):a(n)
    {
        cout<<"构造"<<endl;
         cout<<this<<" "<<this_thread::get_id()<<endl;
    }
    ~A()
    {
        cout<<"析构"<<endl;
         cout<<this<<" "<<this_thread::get_id()<<endl;
    }

};
void myprint(const A  a)
{
    cout<<"start";
    cout<<&a<<endl;
     cout<<"子线程"<<this_thread::get_id()<<endl;
}

int main()
{
    int mvar = 1;
    cout<<"主线程"<<this_thread::get_id()<<endl;
    thread p(myprint,A(mvar));
    p.join();

    cout << "Hello World!" << endl;
}

this_thread::get_id()可以得到线程的id。
在这里插入图片描述

看到前两次复制构造都是在主线程构造的。第三次是在子线程中构造的。
比较奇怪的是0xfc18c8的析构是子线程里的。在myprint里的A对象的this是0x28dfdlc。函数接受的是引用的时候:在这里插入图片描述

0x6a18c8是在主线程构造,在子线程析构的。
隐式复制:在这里插入图片描述
构造和析构在子线程里,比较危险。

#include <iostream>
#include <thread>
using namespace std;
class A
{
public:
    mutable int a;

    A(const A &b):a(b.a)
    {
        cout<<"复制构造"<<endl;
         cout<<this<<" "<<this_thread::get_id()<<endl;
    }
    A( int n):a(n)
    {
        cout<<"构造"<<endl;
         cout<<this<<" "<<this_thread::get_id()<<endl;
    }
    ~A()
    {
        cout<<"析构"<<endl;
         cout<<this<<" "<<this_thread::get_id()<<endl;
    }

};
void myprint(  const A  &a)
{
    cout<<"start";
    cout<<&a<<endl;
    a.a=199;
    cout<<a.a<<endl;
     cout<<"子线程"<<this_thread::get_id()<<endl;
}

int main()
{
    int mvar = 1;
    A a(mvar);
    cout<<"主线程"<<this_thread::get_id()<<endl;
    thread p(myprint,a);
    p.join();
    cout<<a.a<<endl;

    cout << "Hello World!" << endl;
}

这样显然无法改变主线程a对象的属性。在这里插入图片描述
如果想改变,需要用std::ref

#include <iostream>
#include <thread>
using namespace std;
class A
{
public:
    mutable int a;

    A(const A &b):a(b.a)
    {
        cout<<"复制构造"<<endl;
         cout<<this<<" "<<this_thread::get_id()<<endl;
    }
    A( int n):a(n)
    {
        cout<<"构造"<<endl;
         cout<<this<<" "<<this_thread::get_id()<<endl;
    }
    ~A()
    {
        cout<<"析构"<<endl;
         cout<<this<<" "<<this_thread::get_id()<<endl;
    }

};
void myprint(  A  &a)
{
    cout<<"start";
    cout<<&a<<endl;
    a.a=199;
    cout<<a.a<<endl;
     cout<<"子线程"<<this_thread::get_id()<<endl;
}

int main()
{
    int mvar = 1;
    A a(mvar);
    cout<<"主线程"<<this_thread::get_id()<<endl;
    thread p(myprint,ref(a));
    p.join();
    cout<<a.a<<endl;

    cout << "Hello World!" << endl;
}

在这里插入图片描述
在这里插入图片描述

智能指针

#include <iostream>
#include <thread>
#include <memory>
using namespace std;

void myPrint(int* ptn)

{
    cout<<ptn<<endl;
    cout << "thread = " << std::this_thread::get_id() << endl;

}

int main()
{
    // unique_ptr<int> up(new int(10));
    int *p,a=1;
    p=&a;
    cout<<p<<endl;
    //独占式指针只能通过std::move()才可以传递给另一个指针
    //传递后up就指向空,新的ptn指向原来的内存
    //所以这时就不能用detach了,因为如果主线程先执行完,ptn指向的对象就被释放了
    thread myThread(myPrint,p);
    myThread.join();
    //myThread.detach();

    return 0;
}

在这里插入图片描述
传递指针都有风险,额,其实上面的字符串应该也是。

#include <iostream>
#include <thread>
#include <memory>
using namespace std;

void myPrint(unique_ptr<int> ptn)
{
	cout << "thread = " << std::this_thread::get_id() << endl;
}

int main()
{
	unique_ptr<int> up(new int(10));
	//独占式指针只能通过std::move()才可以传递给另一个指针
	//传递后up就指向空,新的ptn指向原来的内存
	//所以这时就不能用detach了,因为如果主线程先执行完,ptn指向的对象就被释放了
	thread myThread(myPrint, std::move(up));
	myThread.join();
	//myThread.detach();

	return 0;
}

智能指针也一样。

数据保护

在这里插入图片描述

#include <iostream>
#include <thread>
#include <vector>
using namespace std;
void TextThread(int n)
{
     cout << "我是线程" << this_thread::get_id() << endl;
     /*  …  */
     cout<<n<<endl;
     cout << "线程" << this_thread::get_id() << "执行结束" << endl;
}
 //main函数里     vector threadagg;
int main(int argc, char const *argv[])
{
    vector<thread> threadagg;
         for (int i = 0; i < 10; ++i)
     {
         threadagg.push_back(thread(TextThread,i));
     }
     for (int i = 0; i < 10; ++i)
     {
         threadagg[i].join();
     }
    return 0;
}

在这里插入图片描述
有可能线程的执行顺序和启动顺序不同,这和系统调度有关。

在这里插入图片描述

在这里插入图片描述

#include <iostream>
#include <thread>
#include <list>
using namespace std;
class A
{
private:
    list<int> q;

public:
    A()
    {cout<<this<<endl<<"构造函数"<<endl;}
    ~A(){cout<<this<<endl<<"析构函数"<<endl;}
    void wq()
    {
        for(int i=0;i<100000;i++)
        {
            q.push_front(i);
            cout<<"写入"<<i<<endl;
        }
    }
    void rq()
    {
        for(int i=0;i<100000;i++)
        {
        if(q.size())
        {
            cout<<"读取"<<q.front();
            q.pop_front();

        }
        else
        {
            cout<<"等待读取"<<endl;
        }
        }
        cout<<"读取结束"<<endl;
    }


};
 //main函数里     vector threadagg;
int main(int argc, char const *argv[])
{
    A a;
    thread w(&A::wq,&a);
    thread r(&A::rq,&a);
    w.join();
    r.join();
    return 0;
}

这种既写又读的应该会出问题,然而我在g++上居然没有出错。

#include <iostream>
#include <thread>
#include <list>
using namespace std;
class A
{
private:
    int q;

public:
    A()
    {cout<<this<<endl<<"构造函数"<<endl;}
    ~A(){cout<<this<<endl<<"析构函数"<<endl;}
    void wq()
    {
        for(int i=0;i<100000;i++)
        {
            q=i;
            cout<<"写入"<<i<<endl;
        }
    }
    void rq()
    {
        for(int i=0;i<100000;i++)
        {
            cout<<q<<endl;
        }
        cout<<"读取结束"<<endl;
    }


};
 //main函数里     vector threadagg;
int main(int argc, char const *argv[])
{
    A a;
    thread w(&A::wq,&a);
    thread r(&A::rq,&a);
    w.join();
    r.join();
    return 0;
}

也不i报错,挺奇怪的,但是这样肯定是有问题的,所以需要线程锁。

互斥量

在这里插入图片描述
一、互斥量(mutex)的基本概念

互斥量就是个类对象,可以理解为一把锁,多个线程尝试用lock()成员函数来加锁,只有一个线程能锁定成功,如果没有锁成功,那么流程将卡在lock()这里不断尝试去锁定。
互斥量使用要小心,保护数据不多也不少,少了达不到效果,多了影响效率。
二、互斥量的用法
包含#include 头文件
2.1 lock(),unlock()

步骤:1.lock(),2.操作共享数据,3.unlock()。
lock()和unlock()要成对使用

#include <iostream>
#include <thread>
#include <list>
#include <mutex>
using namespace std;
class A
{
private:
    list<int> q;
    mutex my_muetx;

public:
    A()
    {cout<<this<<endl<<"构造函数"<<endl;}
    ~A(){cout<<this<<endl<<"析构函数"<<endl;}
    void wq()
    {
        for(int i=0;i<100000;i++)
        {
            my_muetx.lock();
            q.push_front(i);
            my_muetx.unlock();
            cout<<"写入"<<i<<endl;
        }
    }
    void rq()
    {
        for(int i=0;i<100000;i++)
        {
            my_muetx.lock();
        if(q.size())
        {
            cout<<"读取"<<q.front();
            q.pop_front();
            my_muetx.unlock();

        }
        else
        {
            my_muetx.unlock();
            cout<<"等待读取"<<endl;
        }
        }
        cout<<"读取结束"<<endl;
    }


};
 //main函数里     vector threadagg;
int main(int argc, char const *argv[])
{
    A a;
    thread w(&A::wq,&a);
    thread r(&A::rq,&a);
    w.join();
    r.join();
    return 0;
}

尤其注意if条件判断的unlock()配对。
2.2 lock_guard类模板

lock_guard sbguard(myMutex);取代lock()和unlock()
lock_guard构造函数执行了mutex::lock();在作用域结束时,调用析构函数,执行mutex::unlock()

代码:

#include <iostream>
#include <thread>
#include <list>
#include <mutex>
using namespace std;
class A
{
private:
    list<int> q;
    mutex my_muetx;

public:
    A()
    {cout<<this<<endl<<"构造函数"<<endl;}
    ~A(){cout<<this<<endl<<"析构函数"<<endl;}
    void wq()
    {
        for(int i=0;i<100000;i++)
        {
            // my_muetx.lock();
            lock_guard<mutex> lg(my_muetx);
            q.push_front(i);
            // my_muetx.unlock();
            cout<<"写入"<<i<<endl;
        }
    }
    void rq()
    {
        for(int i=0;i<100000;i++)
        {
               lock_guard<mutex> lg(my_muetx);
            if(q.size())
            {
                cout<<"读取"<<q.front();
                q.pop_front();
                // my_muetx.unlock();

            }
            else
            {
                // my_muetx.unlock();
                cout<<"等待读取"<<endl;
            }
        }
        cout<<"读取结束"<<endl;
    }


};
 //main函数里     vector threadagg;
int main(int argc, char const *argv[])
{
    A a;
    thread w(&A::wq,&a);
    thread r(&A::rq,&a);
    w.join();
    r.join();
    return 0;
}

三、死锁
3.1 死锁演示
死锁至少有两个互斥量mutex1,mutex2。

a.线程A执行时,这个线程先锁mutex1,并且锁成功了,然后去锁mutex2的时候,出现了上下文切换。
b.线程B执行,这个线程先锁mutex2,因为mutex2没有被锁,即mutex2可以被锁成功,然后线程B要去锁mutex1.
c.此时,死锁产生了,A锁着mutex1,需要锁mutex2,B锁着mutex2,需要锁mutex1,两个线程没办法继续运行下去。。。

#include <iostream>
#include <thread>
#include <list>
#include <mutex>
using namespace std;
class A
{
private:
    list<int> q;
    mutex my_muetx1,my_muetx2;

public:
    A()
    {cout<<this<<endl<<"构造函数"<<endl;}
    ~A(){cout<<this<<endl<<"析构函数"<<endl;}
    void wq()
    {
        for(int i=0;i<100000;i++)
        {
            // my_muetx.lock();
            lock_guard<mutex> lg(my_muetx1);
            lock_guard<mutex> lg2(my_muetx2);
            q.push_front(i);
            // my_muetx.unlock();
            cout<<"写入"<<i<<endl;
        }
    }
    void rq()
    {
        for(int i=0;i<100000;i++)
        {
               lock_guard<mutex> lg(my_muetx2);
               lock_guard<mutex> lg2(my_muetx1);
            if(q.size())
            {
                cout<<"读取"<<q.front();
                q.pop_front();
                // my_muetx.unlock();

            }
            else
            {
                // my_muetx.unlock();
                cout<<"等待读取"<<endl;
            }
        }
        cout<<"读取结束"<<endl;
    }


};
 //main函数里     vector threadagg;
int main(int argc, char const *argv[])
{
    A a;
    thread w(&A::wq,&a);
    thread r(&A::rq,&a);
    w.join();
    r.join();
    return 0;
}

在这里插入图片描述
这就发生了死锁。
3.2 死锁的一般解决方案:
只要保证多个互斥量上锁的顺序一样就不会造成死锁。
3.3 std::lock()函数模板

std::lock(mutex1,mutex2……); 一次锁定多个互斥量(一般这种情况很少),用于处理多个互斥量。
如果互斥量中一个没锁住,它就等着,等所有互斥量都锁住,才能继续执行。如果有一个没锁住,就会把已经锁住的释放掉(要么互斥量都锁住,要么都没锁住,防止死锁)

#include <iostream>
#include <thread>
#include <list>
#include <mutex>
using namespace std;
class A
{
private:
    list<int> q;
    mutex my_muetx1,my_muetx2;

public:
    A()
    {cout<<this<<endl<<"构造函数"<<endl;}
    ~A(){cout<<this<<endl<<"析构函数"<<endl;}
    void wq()
    {
        for(int i=0;i<100000;i++)
        {
            // // my_muetx.lock();
            lock(my_muetx1,my_muetx2);
            q.push_front(i);
            my_muetx1.unlock();
            my_muetx2.unlock();
            cout<<"写入"<<i<<endl;
        }
    }
    void rq()
    {
        for(int i=0;i<100000;i++)
        {
               // lock_guard<mutex> lg(my_muetx2);
               // lock_guard<mutex> lg2(my_muetx1);
            lock(my_muetx1,my_muetx2);
            if(q.size())
            {
                cout<<"读取"<<q.front();
                q.pop_front();
                // my_muetx.unlock();
                  my_muetx1.unlock();
            my_muetx2.unlock();

            }
            else
            {
                // my_muetx.unlock();
                  my_muetx1.unlock();
            my_muetx2.unlock();
                cout<<"等待读取"<<endl;
            }
        }
        cout<<"读取结束"<<endl;
    }


};
 //main函数里     vector threadagg;
int main(int argc, char const *argv[])
{
    A a;
    thread w(&A::wq,&a);
    thread r(&A::rq,&a);
    w.join();
    r.join();
    return 0;
}

还可以:
3.4 std::lock_guard的std::adopt_lock参数

std::lock_guardstd::mutex my_guard(my_mutex,std::adopt_lock);
加入adopt_lock后,在调用lock_guard的构造函数时,不再进行lock();
adopt_guard为结构体对象,起一个标记作用,表示这个互斥量已经lock(),不需要在lock()。

#include <iostream>
#include <thread>
#include <list>
#include <mutex>
using namespace std;
class A
{
private:
    list<int> q;
    mutex my_muetx1,my_muetx2;

public:
    A()
    {cout<<this<<endl<<"构造函数"<<endl;}
    ~A(){cout<<this<<endl<<"析构函数"<<endl;}
    void wq()
    {
        for(int i=0;i<100000;i++)
        {
            // // my_muetx.lock();
            lock(my_muetx1,my_muetx2);
            lock_guard<mutex> lg(my_muetx1,adopt_lock);
            lock_guard<mutex> lg1(my_muetx2,adopt_lock);
            q.push_front(i);
            // my_muetx1.unlock();
            // my_muetx2.unlock();
            cout<<"写入"<<i<<endl;
        }
    }
    void rq()
    {
        for(int i=0;i<100000;i++)
        {
               // lock_guard<mutex> lg(my_muetx2);
               // lock_guard<mutex> lg2(my_muetx1);
            lock(my_muetx1,my_muetx2);
            lock_guard<mutex> lg(my_muetx1,adopt_lock);
            lock_guard<mutex> lg1(my_muetx2,adopt_lock);
            if(q.size())
            {
                cout<<"读取"<<q.front();
                q.pop_front();
                // my_muetx.unlock();
                  // my_muetx1.unlock();
            // my_muetx2.unlock();

            }
            else
            {
                // my_muetx.unlock();
                  // my_muetx1.unlock();
            // my_muetx2.unlock();
                cout<<"等待读取"<<endl;
            }
        }
        cout<<"读取结束"<<endl;
    }


};
 //main函数里     vector threadagg;
int main(int argc, char const *argv[])
{
    A a;
    thread w(&A::wq,&a);
    thread r(&A::rq,&a);
    w.join();
    r.join();
    return 0;
}

在这里插入图片描述
2.unique_lock的第二个参数
2.1 std::adopt_lock:

表示这个互斥量已经被lock(),即不需要在构造函数中lock这个互斥量了。
前提:必须提前lock
lock_guard中也可以用这个参数
2.2 std::try_to_lock:

尝试用mutx的lock()去锁定这个mutex,但如果没有锁定成功,会立即返回,不会阻塞在那里;
使用try_to_lock的原因是防止其他的线程锁定mutex太长时间,导致本线程一直阻塞在lock这个地方
前提:不能提前lock();
owns_locks()方法判断是否拿到锁,如拿到返回true

#include <iostream>
#include <thread>
#include <list>
#include <mutex>
using namespace std;
class A
{
private:
    list<int> q;
    mutex my_muetx1,my_muetx2;

public:
    A()
    {cout<<this<<endl<<"构造函数"<<endl;}
    ~A(){cout<<this<<endl<<"析构函数"<<endl;}
    void wq()
    {
        for(int i=0;i<100000;i++)
        {
            // // my_muetx.lock();
            // lock(my_muetx1,my_muetx2);
            lock_guard<mutex> lg(my_muetx1);
            chrono::milliseconds dura(1);
            this_thread::sleep_for(dura);
            // lock_guard<mutex> lg1(my_muetx2,adopt_lock);
            q.push_front(i);
            // my_muetx1.unlock();
            // my_muetx2.unlock();
            cout<<"写入"<<i<<endl;
        }
    }
    void rq()
    {
        for(int i=0;i<100000;i++)
        {
               // lock_guard<mutex> lg(my_muetx2);
               // lock_guard<mutex> lg2(my_muetx1);
            unique_lock<mutex> ml(my_muetx1,try_to_lock);
            // lock(my_muetx1,my_muetx2);
            // lock_guard<mutex> lg(my_muetx1,adopt_lock);
            // lock_guard<mutex> lg1(my_muetx2,adopt_lock);
            if (ml.owns_lock())
            {
                cout<<"get_lock"<<endl;

                if(q.size())
                {
                    cout<<"读取"<<q.front();
                    q.pop_front();
                    // my_muetx.unlock();
                      // my_muetx1.unlock();
                // my_muetx2.unlock();

                }
                else
                {
                    // my_muetx.unlock();
                      // my_muetx1.unlock();
                // my_muetx2.unlock();
                    cout<<"等待读取"<<endl;
                }
            }
            else cout<<"未拿到锁"<<endl;
        }
        cout<<"读取结束"<<endl;
    }


};
 //main函数里     vector threadagg;
int main(int argc, char const *argv[])
{
    A a;
    thread w(&A::wq,&a);
    thread r(&A::rq,&a);
    w.join();
    r.join();
    return 0;
}

在这里插入图片描述
2.3 std::defer_lock:

如果没有第二个参数就对mutex进行加锁,加上defer_lock是始化了一个没有加锁的mutex
不给它加锁的目的是以后可以调用unique_lock的一些方法
前提:不能提前lock在这里插入图片描述
在这里插入图片描述
lock(),unlock()和try_lock()都要和defer_lock一起使用,而release不需要。

#include <iostream>
#include <thread>
#include <list>
#include <mutex>
using namespace std;
class A
{
private:
    list<int> q;
    mutex my_muetx1,my_muetx2;

public:
    A()
    {cout<<this<<endl<<"构造函数"<<endl;}
    ~A(){cout<<this<<endl<<"析构函数"<<endl;}
    void wq()
    {
        for(int i=0;i<100000;i++)
        {
            // // my_muetx.lock();
            // lock(my_muetx1,my_muetx2);
            lock_guard<mutex> lg(my_muetx1);
            // chrono::milliseconds dura(1);
            // this_thread::sleep_for(dura);
            // lock_guard<mutex> lg1(my_muetx2,adopt_lock);
            q.push_front(i);
            // my_muetx1.unlock();
            // my_muetx2.unlock();
            cout<<"写入"<<i<<endl;
        }
    }
    void rq()
    {
        for(int i=0;i<100000;i++)
        {
               // lock_guard<mutex> lg(my_muetx2);
               // lock_guard<mutex> lg2(my_muetx1);
            unique_lock<mutex> ml(my_muetx1,try_to_lock);
            // lock(my_muetx1,my_muetx2);
            // lock_guard<mutex> lg(my_muetx1,adopt_lock);
            // lock_guard<mutex> lg1(my_muetx2,adopt_lock);
            if (ml.owns_lock())
            {
                cout<<"get_lock"<<endl;
                mutex *pt=ml.release();

                if(q.size())
                {
                    cout<<"读取"<<q.front();
                    q.pop_front();
                    // my_muetx.unlock();
                      // my_muetx1.unlock();
                // my_muetx2.unlock();
                    pt->unlock();

                }
                else
                {
                    // my_muetx.unlock();
                      // my_muetx1.unlock();
                // my_muetx2.unlock();
                    cout<<"等待读取"<<endl;
                    pt->unlock();
                }
            }
            else cout<<"未拿到锁"<<endl;
        }
        cout<<"读取结束"<<endl;
    }


};
 //main函数里     vector threadagg;
int main(int argc, char const *argv[])
{
    A a;
    thread w(&A::wq,&a);
    thread r(&A::rq,&a);
    w.join();
    r.join();
    return 0;
}

4.unique_lock所有权的传递
unique_lock myUniLock(myMutex);把myMutex和myUniLock绑定在了一起,也就是myUniLock拥有myMutex的所有权

  1. 使用move转移

myUniLock拥有myMutex的所有权,myUniLock可以把自己对myMutex的所有权转移,但是不能复制。
unique_lock myUniLock2(std::move(myUniLock));
现在myUniLock2拥有myMutex的所有权。

#include <iostream>
#include <thread>
#include <list>
#include <mutex>
using namespace std;
class A
{
private:
    list<int> q;
    mutex my_muetx1,my_muetx2;

public:
    A()
    {cout<<this<<endl<<"构造函数"<<endl;}
    ~A(){cout<<this<<endl<<"析构函数"<<endl;}
    void wq()
    {
        for(int i=0;i<100000;i++)
        {
            // // my_muetx.lock();
            // lock(my_muetx1,my_muetx2);
            lock_guard<mutex> lg(my_muetx1);
            // chrono::milliseconds dura(1);
            // this_thread::sleep_for(dura);
            // lock_guard<mutex> lg1(my_muetx2,adopt_lock);
            q.push_front(i);
            // my_muetx1.unlock();
            // my_muetx2.unlock();
            cout<<"写入"<<i<<endl;
        }
    }
    void rq()
    {
        for(int i=0;i<100000;i++)
        {
               // lock_guard<mutex> lg(my_muetx2);
               // lock_guard<mutex> lg2(my_muetx1);
            unique_lock<mutex> ml(my_muetx1,try_to_lock);
            unique_lock<mutex> sg(move(ml));
            // lock(my_muetx1,my_muetx2);
            // lock_guard<mutex> lg(my_muetx1,adopt_lock);
            // lock_guard<mutex> lg1(my_muetx2,adopt_lock);
            if (sg.owns_lock())
            {
                cout<<"get_lock"<<endl;
                mutex *pt=sg.release();

                if(q.size())
                {
                    cout<<"读取"<<q.front();
                    q.pop_front();
                    // my_muetx.unlock();
                      // my_muetx1.unlock();
                // my_muetx2.unlock();
                    pt->unlock();

                }
                else
                {
                    // my_muetx.unlock();
                      // my_muetx1.unlock();
                // my_muetx2.unlock();
                    cout<<"等待读取"<<endl;
                    pt->unlock();
                }
            }
            else cout<<"未拿到锁"<<endl;
        }
        cout<<"读取结束"<<endl;
    }


};
 //main函数里     vector threadagg;
int main(int argc, char const *argv[])
{
    A a;
    thread w(&A::wq,&a);
    thread r(&A::rq,&a);
    w.join();
    r.join();
    return 0;
}

  1. 在函数中return一个临时变量,即可以实现转移
#include <iostream>
#include <thread>
#include <list>
#include <mutex>
using namespace std;
class A
{
private:
    list<int> q;
    mutex my_muetx1,my_muetx2;

public:
    unique_lock<mutex> aFunction()
    {
        unique_lock<mutex> myUniLock(my_muetx1,try_to_lock);
        //移动构造函数那里讲从函数返回一个局部的unique_lock对象是可以的
        //返回这种局部对象会导致系统生成临时的unique_lock对象,并调用unique_lock的移动构造函数
        return myUniLock;
    }

    A()
    {cout<<this<<endl<<"构造函数"<<endl;}
    ~A(){cout<<this<<endl<<"析构函数"<<endl;}
    void wq()
    {
        for(int i=0;i<100000;i++)
        {
            // // my_muetx.lock();
            // lock(my_muetx1,my_muetx2);
            lock_guard<mutex> lg(my_muetx1);
            // chrono::milliseconds dura(1);
            // this_thread::sleep_for(dura);
            // lock_guard<mutex> lg1(my_muetx2,adopt_lock);
            q.push_front(i);
            // my_muetx1.unlock();
            // my_muetx2.unlock();
            cout<<"写入"<<i<<endl;
        }
    }
    void rq()
    {
        for(int i=0;i<100000;i++)
        {
               // lock_guard<mutex> lg(my_muetx2);
               // lock_guard<mutex> lg2(my_muetx1);
            // unique_lock<mutex> ml(my_muetx1,try_to_lock);
            unique_lock<mutex> sg=aFunction();
            // lock(my_muetx1,my_muetx2);
            // lock_guard<mutex> lg(my_muetx1,adopt_lock);
            // lock_guard<mutex> lg1(my_muetx2,adopt_lock);
            if (sg.owns_lock())
            {
                cout<<"get_lock"<<endl;
                mutex *pt=sg.release();

                if(q.size())
                {
                    cout<<"读取"<<q.front();
                    q.pop_front();
                    // my_muetx.unlock();
                      // my_muetx1.unlock();
                // my_muetx2.unlock();
                    pt->unlock();

                }
                else
                {
                    // my_muetx.unlock();
                      // my_muetx1.unlock();
                // my_muetx2.unlock();
                    cout<<"等待读取"<<endl;
                    pt->unlock();
                }
            }
            else cout<<"未拿到锁"<<endl;
        }
        cout<<"读取结束"<<endl;
    }


};
 //main函数里     vector threadagg;
int main(int argc, char const *argv[])
{
    A a;
    thread w(&A::wq,&a);
    thread r(&A::rq,&a);
    w.join();
    r.join();
    return 0;
}

这里牵扯到了移动构造。
回顾左值和右值。

  1. 左值和右值

(1)两者区别:

①左值:能对表达式取地址、或具名对象/变量。一般指表达式结束后依然存在的持久对象。

②右值:不能对表达式取地址,或匿名对象。一般指表达式结束就不再存在的临时对象。

(2)右值的分类

①将亡值(xvalue,eXpiring value):指生命期即将结束的值,一般是跟右值引用相关的表达式,这样表达式通常是将要被移动的对象,如返回类型为T&&的函数返回值(如std::move)、经类型转换为右值引用的对象(如static_cast<T&&>(obj))、xvalue类对象的成员访问表达式也是一个xvalue(如Test().memberdata,注意Test()是个临时对象)

②纯右值(prvalue, PureRvalue):按值返回的临时对象、运算表达式产生的临时变对象、原始字面量和lambda表达式等。
  3)C++11中的表达式
在这里插入图片描述

①表达式是由运算符(operator)和运算对象(operand)构成的计算式。字面值和变量是最简单的表达式,函数的返回值也被认为是表达式。

②表达式是可求值的,对表达式求值将得到一个结果,这个结果有两个属性:类型和值类别,而表达式的值类别必属于左值、将亡值或纯右值三者之一。

③“左值”和“右值”是表达式结果的一种属性。通常用“左值”来指代左值表达式,用“右值”指代右值表达式。
  参考https://blog.csdn.net/linuxheik/article/details/78929128
移动构造
C++ 11 标准中提供了一种新的构造方法–移动构造
C++11 之前,如果要将源对象的状态转移到目标对象只能通过复制。在某些情况下,我们有没有必要复制对象----只需要移动他们
移动语义:源对象资源的控制权全部交给目标对

在这里插入图片描述
什么时候用移动构造?
临时对象即将消亡,但里面的数据还是要用的,所以用移动构造

#include <iostream>
using namespace std;
class IntNum
{
public:
IntNum(int x =0): xpt(new int(x))//构造函数
    {
    cout <<"Calling constructor"<< endl;
    }
    IntNum( const IntNum & n):xpt( new int(*n.xpt))
    {//复制构造函数
    cout <<"Calling copy constructor. "<< endl;
    }
    ~ IntNum()
    {//析构函数
    delete xpt;
    cout<<"Destructing."<<endl;
    }
    int getInt()
    {
        return *xpt;
    }
private:
    int *xpt;
};
IntNum getNum()
{
    IntNum a;
    return a;
}
int main(int argc, char const *argv[])
{
    cout<<getNum().getInt();
    return 0;
}

移动构造:

#include <iostream>
using namespace std;
class IntNum
{
public:
IntNum(int x =0): xpt(new int(x))//构造函数
    {
    cout <<"Calling constructor"<< endl;
    }
    IntNum( const IntNum & n):xpt( new int(*n.xpt))
    {//复制构造函数
    cout <<"Calling copy constructor. "<< endl;
    }
    IntNum( IntNum &&n):xpt(n.xpt)
    {
        n.xpt=nullptr;
        cout<<"move constructor"<<endl;

    }
    ~ IntNum()
    {//析构函数
    delete xpt;
    cout<<"Destructing."<<endl;
    }
    int getInt()
    {
        return *xpt;
    }
private:
    int *xpt;
};
IntNum getNum()
{
    IntNum a;
    return a;
}
int main(int argc, char const *argv[])
{
    cout<<getNum().getInt();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值