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;
}