C++11多线程学习笔记
1. 引言
本文是笔者观看该视频所做的笔记,本文所涉及的代码均是在Windows系统下的Visual Studio2017环境下编译的。
如有错误,望指正,笔者的邮箱为:wuxiaofang555555@163.com。
- 并发:多个任务同时执行。
- 进程:运行一个.exe文件,就是开始了一个进程。一个进程一定包含一个主线程。
- 线程:一个进程包含一个主线程,当然用于也可以自定义线程,即形成多线程。C++11标准里提供了多线程。
- Windows系统下用的库,Linux用的库。
2. 线程的启动、结束,创建线程
2.1 用函数创建
//code2.1
#include<iostream>
#include<thread>
using namespace std;
void thread1(){
cout<<"my thread is starting"<<endl;
//...
cout<<"my thread is ending"<<endl;
}
int main(){
cout<<"main thread is starting"<<endl;
thread mytobj(thread1);
//mytobj.detach();//分离thread1线程和主线程,两条线程各自执行,互补干扰
mytobj.join(); //阻塞主线程并等待thread1线程执行完毕,主线程再结束
cout<<"main thread is ending"<<endl;
return 0;
}
2.2 用类创建线程
//code2.2
class T{
int m_a;
public:
void opertor()(){
cout<<"my thread is starting"<<endl;
//...
cout<<"my thread is ending"<<endl;
}
};
int main(){
int a=6;
T myt(a);
thread mytobj(myt); //这里的myt是值传递
mytobj.join();
return 0;
}
2.3 用lambda表达式创建线程
//code2.3
int main(){
auto mythread=[]{
cout<<"my thread is starting"<<endl;
//...
cout<<"my thread is ending"<<endl;
}
thread mytobj(mythread);
mytobj.join();
return 0;
}
3. 线程传参,detach()坑,成员函数做线程函数
3.1 传递临时对象作为线程参数
- 陷阱一
- 线程参数中有指针时,不能使用
detach()
, 如code3.1,可改为引用,如code3.2
- 线程参数中有指针时,不能使用
- 陷阱二
- code3.2中的
mbuf
是什么时候转为string,存在mbuf
内存被回收了之后,才转换为string
的情况,会出现不可预料的后果,解决方法:传参时就进行转换,如code3.3
- code3.2中的
- 总结
- 若传递内置类型等简单类型(一般指内置类型)参数,建议采用值传递,不用引用,一般引用在线程传参时也是无效的,最后仍是值传递
- 如果传递类对象,避免隐士转换,如code3.4就避免了这种情况
- 线程id概念:
this_thread::get_id()
可以访问该线程的id
//code3.1:此代码不能用detach(),因为线程myprint的线程参数中有指针
void myprint(const int &mvar, char* mbuf) {
cout << mvar << endl; //mvar并不是var的引用,是值传递,故即使使用detach(),也不会出错
cout << mbuf << endl; //由于mbuf和主线程中的临时对象mbuf指向同一个地址,
//故若使用detach()会出问题
}
int main() {
int var = 56;
int& mvar = var;
char mbuf[] = "this is a test";
thread mytobj(myprint, mvar, mbuf);//第一个参数为线程入口、后续参数依次为传递给线程的实参
//mytobj.detach();
mytobj.join();
return 0;
}
//code3.2:将线程myprint的线程参数中的指针改为引用。还是有问题
void myprint(const int &mvar, const string& mbuf) {
cout << mvar << endl;
cout << mbuf.c_str() << endl;
}
int main() {
int var = 56;
int& mvar = var;
char mbuf[] = "this is a test";
thread mytobj(myprint, mvar, mbuf);
mytobj.detach();
//mytobj.join();
return 0;
}
//code3.3:在线程传递参数时就将mbuf转化为string类型
void myprint(const int &mvar, const string& mbuf) {
cout << mvar << endl;
cout << mbuf.c_str() << endl;
}
int main() {
int var = 56;
int& mvar = var;
char mbuf[] = "this is a test";
thread mytobj(myprint, mvar, string(mbuf));//在这里直接将mbuf转换为string类型
mytobj.detach();
//mytobj.join();
return 0;
}
//code3.4
class A {
int m_a;
public:
A(int a):m_a(a){ } //构造函数
A(const A& myA):m_a(myA.m_a){ } //复制构造函数
~A(){ } //析构函数
int get() { return m_a; } //获得m_a值
void reset(int a) { m_a = a; } //重新设置m_a的值
};
void print(const A & myA){
cout << "子线程id: " << this_thread::get_id() << endl;
cout << "myA的地址为: " << &myA << endl;
}
int main(){
int a=56;
A my(56);
cout << "主线程id: " << this_thread::get_id() << endl; //可以访问该线程的id
cout << "my的地址为: "<<&my << endl;
//thread mytobj(myprint,my);
thread mytobj(myprint,A(a));
mytobj.join();
return 0;
}
/******输出结果如下******
主线程id: 2072
my的地址为: 0072F990
子线程id: 3416
myA的地址为: 00D4CF28
***********************/
//从结果可以看出,A类型的my对象是通过值传递来进行线程传参的
3.2 传递类对象、智能指针作为线程参数
- std::ref用于引用,可修改对象值,如code3.5
- 使用智能指针
unique_ptr<int>
作为线程形参,如code3.6
//code3.5:测试ref
//类类型A的定义与code3.4相同
void myprint(A &myA) {
cout << "myA的地址为: " << &myA << endl;
myA.reset(68);//修改m_a的值
}
int main(){
A my(56);
cout << "my的地址为: "<<&my << endl;
cout << "my的m_a: "<<my.get() << endl;
thread mytobj(myprint,ref(my));
mytobj.join();
cout << "my的m_a: "<<my.get() << endl;
}
/******输出结果如下******
my的地址为: 00D3FD8C
my的m_a: 56
myA的地址为: 00D3FD8C
my的m_a: 68
***********************/
//说明ref实现了真正的引用
//code3.6:unique_ptr<int>
void myprint2(unique_ptr<int> a) {
cout << *a << endl;
}
int main(){
unique_ptr<int> my(new int(56));
thread mytobj(myprint2, move(my));
mytobj.join();
}
3.3 用成员函数指针做线程函数
//code3.7
class A {
int m_a;
public:
//...省略了其他函数,与code3.4相同
void thread_work(int var) { cout << var << endl; }//使用该成员函数作为线程入口
};
int main(){
int var = 66;
A my(56);
thread mytobj(&A::thread_work, my, var);
//第一个参数为成员函数地址,第二个为类类型对象,第三个之后为该成员函数参数
mytobj.join();
return 0;
}
4. 创建多个线程、数据共享问题分析
4.1 创建和等待多个线程
//code4.1:创建多个线程
void print3(int num){
cout << "线程编号: " << num << endl;
}
int mian(){
vector<thread> mytobjs;
for (int i = 0; i < 10; ++i) { //批量创建
mytobjs.push_back(thread(myprint3, i));
}
for (auto iter = mytobjs.begin(); iter != mytobjs.end(); ++iter) {
iter->join();
}
return 0;
}
4.2 数据共享
- 只读数据:可以随便访问,如code4.2
- 有读有写:需要限制,写的时候,不能读,在第5节详细讲述
//code4.2
int a[] = { 1,2,3,4,5 };
void myprint3(int num) {
cout << "线程编号: " << num << " " << a[0] << a[1] << a[2] << a[3] << endl;
}
int main(){
//...与code.1相同
}
5. 互斥量,死锁
5.1 互斥量(mutex)概念
- 类对象,理解为一把锁,多个线程使用
lock()
成员函数去锁,只有一个线程能够锁成功
5.2 互斥量(mutex)用法
lock()
,unlock()
要成对使用,如code5.1lock_guard
类模板,智能锁,不能和lock混用,如code5.2
//code5.1:使用lock()和unlcok()
class B {
list<int> m_RecQueue;
mutex m_mutex;
public:
void inCommand() {
for (int i = 0; i < 10000; ++i) {
cout << "执行inCommand,插入一个元素" << endl;
m_mutex.lock(); //锁住
m_RecQueue.push_back(i);
m_mutex.unlock(); //开锁
}
}
void outCommand() {
for (int i = 0; i < 10000; ++i) {
int command = 0;
bool result = commandOut(command);
if (result) { //消息不为空
cout << "执行outommand,读出一个元素: " << command << endl;
}
else { //消息为空
cout << "执行outommand,但指令里面为空" << endl;
}
}
}
bool commandOut(int &command) {
m_mutex.lock(); //锁住
if (!m_RecQueue.empty()) {
command = m_RecQueue.front();
m_RecQueue.pop_front();
m_mutex.unlock();//开锁,注意两个返回分支的开锁
return true;
}
m_mutex.unlock(); //开锁,注意两个返回分支的开锁
return false;
}
};
int main(){
B my;
thread myOutobj1(&B::outCommand, &my);//第二个参数用引用,才能保证此案成里用的是同一个对象
thread myInobj2(&B::inCommand, &my);
myOutobj1.join();
myInobj2.join();
}
//code5.2:使用lock_guard类模板
class B{
void inCommand() {
for (int i = 0; i < 10000; ++i) {
cout << "执行inCommand,插入一个元素" << endl;
{
lock_guard<mutex> mymutex(m_mutex);
m_RecQueue.push_back(i);
}
}
}
bool commandOut(int &command) {
lock_guard<mutex> mymutex(m_mutex);
//lock_guard构造函数里执行了mutex::lock()
//lock_guard析构函数里执行mutex::unlock()
if (!m_RecQueue.empty()) {
command = m_RecQueue.front();
m_RecQueue.pop_front();
return true;
}
return false;
}
};
5.3 死锁
- 死锁情况:当有两个互斥量1和2,两个线程A和B。线程A先锁了1,再锁2;而线程B先锁了2,再锁1;若在某个时刻出现线程A只锁住了1,需要锁2,而线程B只锁住了2,需要锁1,故出现死锁情况。
- 死锁一般解决方案:保证两个互斥量上锁的顺序量一致即不会出现死锁情况;可以同时锁住两个互斥量,也可以防止死锁,即使用如下的std::lock()函数模板。
std::lock()
函数模板- 可以同时锁住多个互斥量,不会出现死锁情况,但是需要用户自己使用**
unlock()
**解锁,如code5.3
- 可以同时锁住多个互斥量,不会出现死锁情况,但是需要用户自己使用**
std::lock_guard
类模板的std::adopt_lock
参数- 和
std::lock()
同时使用,可以避免用户自己解锁 std::lock_guard<mutex> guard(m_mutex,std::adopt_lock)
表示局部对象guard
创建时不调用mutex::lock()
,如code5.4
- 和
//code5.3:使用std::lock()同时锁两个互斥量,但需要分别自己解锁
class B{
list<int> m_RecQueue;
mutex m_mutex1;
mutex m_mutex2;
public:
void inCommand() {
//...省略了一些代码,和code5.相似
std::lock(m_mutex1,m_mutex2); //同时锁两个互斥量
m_RecQueue.push_back(i);
m_mutex1.unlock(); // m_mutex1解锁
m_mutex2.unlock(); // m_mutex2解锁
//...
}
};
//code5.4:使用std::lock_guard类模板的std::adopt_lock参数,不需要用户自己解锁
class B{
//...
public:
void inCommand() {
//...省略了一些代码,和code5.1相似
std::lock(m_mutex1,m_mutex2); //同时锁两个互斥量
std::lock_guard<mutex> guard1(m_mutex1,std::adopt_lock);//不调用mutex::lock()
std::lock_guard<mutex> guard2(m_mutex2,std::adopt_lock);//不调用mutex::lock()
m_RecQueue.push_back(i);
//...
}
};
6. unique_lock
6.1 unique_lock取代lock_guard
-
unique_lock
也是一个类模板 -
unique_lock的第二个参数
std::adopt_lock
:表示已经lock
过了,创建对象时不调用mutex::lock()
, 这里和lock_guard
相似std::try_to_lock
:尝试锁互斥量,可以调用该类对象的owns_lock()
函数判断是否锁成功, code6.1std::defer_lock
:创建没有加锁的对象,code6.2
//code6.1:try_to_lock class B{ //... void inCommand() { //...省略了一些代码,和code5.1相似 unique_lock<mutex> guard1(m_mutex1, try_to_lock);//尝试锁住互斥量 if (guard1.owns_lock()) //调用owns_lock()函数判断是否锁成功 cout << "尝试锁成功了" << endl; else cout << "尝试锁失败了" << endl; } };
//code6.2:defer_lock class B{ //... void inCommand() { //... unique_lock<mutex> guard1(m_mutex1, defer_lock);//创建没有加锁的对象 guard1.lock(); //后续不用用户解锁 m_RecQueue.push_back(i); } };
6.2 unique_lock的成员函数
lock()
:加锁,如上述code6.2unlock()
:解锁,想要临时处理一些情况时,可以先解锁try_lock()
: 尝试加锁,和使用参数差不多release()
:返回它所管理的mutex对象指针,并释放所有权,即unique_lock
对象与之绑定的mutex
对象不再有联系。如code6.3
//code6.3:release
class B{
//...
void inCommand() {
//...
unique_lock<mutex> guard1(m_mutex1);
mutex* ptr = guard1.release();//guard1和m_mutex1脱离了关系
m_RecQueue.push_back(i);
ptr->unlock(); //需要自己解锁
}
};
6.3 unique_lock所有权的传递
-
unique_lock<mutex> guard1(m_mutex1)
-
guard1
拥有m_mutex1
的所有权 -
guard1
可以把自己对m_mutex的所有权转移给其他的unique_lock
对象,但是不能复制,如code6.4
//code6.4:uniqu_lock所有权转移
//...
unique_lock<mutex> guard1(m_mutex1);
unique_lock<mutex> guard2(move(guard1));//将所有权转移给guard
//...
6.4 lock() unlock()、std::lock()函数模板、lock_guard类模板、unique_guard类模板区别
- lock() unlock() :互斥量(对象)的成员函数
- std::lock()函数模板:可以同时锁多个互斥量
- lock_guard类模板:有自己的析构函数,比第一个方便,但是不灵活
- unique_guard类模板:方便,灵活(可以设置构造时,是否加锁,还可以调用解锁成员函数等);所有权能够转移
7. 单例设计模式数据共享分析、解决
- 设计模式
7.1 单例设计模式
- 单例类:整个项目中,有某个或者某些特殊的类,属于该类的对象,只能创建1个,如code7.1
//code7.1:单例类
class singleClass {
static singleClass* m_instance;
private:
singleClass(){ } //将构造函数私有化
public:
static singleClass* getInstance() {
if (m_instance == nullptr) {
m_instance = new singleClass();
static free_memory cl;
}
return m_instance;
}
class free_memory { //类套类,为了释放内存,为了避免内存泄漏
public:
~free_memory() {
if (m_instance) {
delete m_instance;
m_instance = nullptr;
}
}
};
};
singleClass* singleClass::m_instance = nullptr;//静态数据成员初始化
int main() {
singleClass* A = singleClass::getInstance();
return 0;
}
7.2 单例设计模式共享数据问题分析、解决
- 在多线程时,创建类时的函数
getInstance()
使用互斥量,且需要修改getInstance()
//code7.2:使用互斥量
mutex mymutex;
class singleClass{
//...
static singleClass* getInstance() {
if (m_instance == nullptr) { //双重锁定(检查)
unique_lock<mutex> mutex1(mymutex);
if (m_instance == nullptr) {
m_instance = new singleClass();
static free_memory cl;
}
}
return m_instance;
}
//...
};
void mythread() { //子线程入口
cout << "子线程开始" << endl;
singleClass* A = singleClass::getInstance();
cout << "子线程结束" << endl;
}
int main() {
thread my1(mythread);
thread my2(mythread);
my1.join();
my2.join();
return 0;
}
7.3 std::call_once()
- 第二个参数是一个函数名,如函数名为
a()
call_once()
保证a()
函数只被调用一次call_once()
具备互斥量这种能力,而且效率上,比互斥量消耗的资源更少call_once()
需要与一个标记结合使用,即std::once_flag
, 当调用一次a()
函数后,此标记为“已标记”,故不再调用
//code7.3:使用call_once()
once_flag flag;
class singleClass {
//...
static singleClass* createInstance() {
m_instance = new singleClass();
static free_memory cl;
}
static singleClass* getInstance() {
call_once(flag, createInstance());//函数createInstance()只被调用一次
return m_instance;
}
//...
};
8. condition_variable、wait、notify_one、notify_all
-
条件变量类condition_variable
wait()
- 第一个参数为
unique_lock
类型 - 第二参数为
lambda
表达式,若返回为true
,则往下执行;若返回为false
,则互斥量解锁,此进程卡在这儿,等待被唤醒如
- 第一个参数为
notify_one()
- 唤醒一次
wait()
- 唤醒一次
notify_all()
- 可以唤醒很多个
wait()
- 可以唤醒很多个
//code8.1:condition_variable class Command { list<int> m_RecQueue; mutex m_mutex1; condition_variable m_con; //条件变量类对象 public: void inCommand() { for (int i = 0; i < 10000; ++i) { cout << "执行inCommand,插入一个命令" << endl; unique_lock<mutex> guard1(m_mutex1); m_RecQueue.push_back(i); m_con.notify_one(); //唤醒wait() } } void outCommand() { while (true) { unique_lock<mutex> guard1(m_mutex1); m_con.wait(guard1, [this]() { //第二参数为lamda表达式,若返回为true,则往下执行; 若返回为false,则guard1解锁,此进程卡在这儿,等待被唤醒 if (!m_RecQueue.empty()) return true; else return false; }); int command = m_RecQueue.front(); m_RecQueue.pop_front(); cout << "取出一个命令" << command << endl; } } }; int main() { Command my; thread myOutobj1(&Command::outCommand, &my); thread myInobj2(&Command::inCommand, &my); myOutobj1.join(); myInobj2.join(); return 0; }
9. async、future、packaged_task、promise
9.1 async函数模板、future类模板创建后台任务
async
用来自动启动一个异步任务,可以通过future
获取线程返回值。如code9.1、code9.2launch::deferred
表示线程入口函数调用被延迟到调用future
的get()
或wait()
时才执行,没创建自线程,代码是在主线程中运行的。launch::async
表示子线程立马就创建运行了,该参数也是默认情况
//code9.1:线程入口为函数
int mythread(int var) {
cout << "mythread start, id: " << this_thread::get_id() << endl;
chrono::milliseconds dura(5000); //定义5秒
this_thread::sleep_for(dura); //停顿dura时间,这里是5秒
cout << "mythread end, id: " << this_thread::get_id() << endl;
return 2*var;
}
int main() {
cout << "main, id: " << this_thread::get_id() << endl;
int var = 6;
future<int> result = async(mythread,var);
//future<int> result = async(launch::async, mythread); //和上一句效果一样
//future<int> result = async(launch::deferred, mythread); //延迟运行线程中的内容
cout << "continue..." << endl;
cout << result.get() << endl; //get()可以获得线程mythread的返回值
//result.wait(); //wait()只等待线程mythread执行完毕
return 0;
}
//code9.2:线程入口为类的成员函数
class test {
public:
int mythread(int var) {
cout << "mythread start, id: " << this_thread::get_id() << endl;
chrono::milliseconds dura(5000); //定义5秒
this_thread::sleep_for(dura); //停顿dura时间,这里是5秒
cout << "mythread end, id: " << this_thread::get_id() << endl;
return 2 * var;
}
};
int main() {
cout << "main, id: " << this_thread::get_id() << endl;
test my;
int var = 6;
future<int> result = async(&test::mythread, &my, var);
//future<int> result = async(launch::async, &test::mythread, &my, var);
//future<int> result = async(launch::deferred, &test::mythread, &my, var);
cout << "continue..." << endl;
cout << result.get() << endl;
//result.wait();
return 0;
}
9.2 packaged_task类模板
- 打包任务,把任务包装起来,如code9.3
//code9.3:packaged_task
//线程入口函数与code9.1相同
int main(){
cout << "main, id: " << this_thread::get_id() << endl;
int var = 6;
packaged_task<int(int)> mypt(mythread);//把函数mythread通过packaged_task包装起来
thread t1(ref(mypt), 5);//线程开始执行,第二个参数为线程入口函数的参数
t1.join();
future<int> result = mypt.get_future();
cout << result.get() << endl;
return 0;
}
9.3 promise
- 能够在某个线程中给它赋值,然后在其他线程中取出,如code9.4, code9.5
//code9.4:promise
void mythread(promise<int> & tmp, int var) {
int result = 2 * var;
tmp.set_value(result);
}
int main() {
promise<int> pro;
thread my(mythread, ref(pro), 5);
my.join();
future<int> ful = pro.get_future();//promise和future绑定,用于获取相乘返回值
cout << ful.get() << endl;
void mythread1(promise<int> & tmp, int var) {
int result = 2 * var;
tmp.set_value(result);
}
void mythread2(future<int> & tmp) {
int result = tmp.get();
cout << "mythread2: " << result << endl;
}
int main() {
promise<int> pro;
thread my1(mythread1, ref(pro), 5);
my1.join();
future<int> ful = pro.get_future();//promise和future绑定,用于获取相乘返回值
thread my2(mythread2, ref(ful)); //在线程mythread2中访问线程mythread1获取的值
my2.join();
}
10. future 其他成员函数、share_future、atomic
future
的其他成员函数shared_future
- 原子操作
atomic
11. windows临界区、其他各种mutex互斥量
- windows临界区可以理解为一种互斥量