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.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.1
  • lock_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.1
    • std::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.2
  • unlock():解锁,想要临时处理一些情况时,可以先解锁
  • 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.2
  • launch::deferred表示线程入口函数调用被延迟到调用futureget()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临界区可以理解为一种互斥量
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值