c++多线程(一)

tech2024-03-13  78

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的所有权

使用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; } 在函数中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)两者区别:

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

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

(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)