C++11多线程

1,667 阅读8分钟

C++11多线程

一、线程的创建及初始化

#include<iostream>
#include<thread>

class MyClass
{
	public void opertator()()
    {
        
    }
}
void myFunc()
{
    
}
int mian()
{
 	//创建一个线程对象但没有相关联的线程
    std::thread tid1;
	//传递可调用对象给线程构造函数
    //传递函数指针
    std::thread tid2(myFunc);
    //传递仿函数
    std::thread tid3{MyClass()};
}

注意,第三个线程的创建我传递的是一个MyClass的匿名对象,并用新的统一初始化语法大括号初始化的tid3变量,如果使用小括号初始化,则编译器会将其解析成函数声明,而非对象创建。例如:

//这条语句,编译器会将其解析为:声明了一个函数,函数名为tid3,接受一个MyClass()函数
//对象指针的参数,返回值是std::thread
std::thread tid3(MyClass());
//除了使用大括号初始化方法,也可以创建一个MyClass对象,初始化
MyClass m;
std::thread tid4(m);

线程对象没有拷贝构造和赋值函数

​ std::thread 对象和 std::unique_ptr 一样不支持赋值操作,但是支持移动操作,从而可以将一个线程的所有权转移给另一个线程对象。

std::thread tid1(myFunc);
//此时tid1不再有相关联的执行线程,执行myFunc函数的线程所有权转移到tid2中
std::thread tid2=std::move(tid1);
//对临时对象进行一定操作是隐式的,可以不加std::move()
tid1=std::thread(myFunc);

不能转移线程所有权给一个已经拥有线程所有权的线程对象

std::thread tid1(myFunc);
std::thread tid2=std::move(tid1);
tid1=std::thread(myFunc);
tid1=std::move(tid2);
//此时程序将被终止

如果在线程对象析构前,未显式的结合或分离线程,那么程序将会被终止

std::thread tid1(myFunc);
std::thread tid2(myFunc);
//显示的结合一个线程,主线程将会中断,等待子线程结束,然后继续向下运行
tid1.join();
//显示的分离一个线程,主线程将不等待子线程结束,直接继续向下运行
tid2.detach();

二、给线程函数传参

在给线程传递引用参数时,需要使用std::ref(value)或者&value显示指出传递的是引用,因为thread是模板类,会根据传入的实参进行推导形参。

void myFunc1(int a,int b)
{
    
}
void myFunc2(int &a,int &b)
{
    
}
int mian()
{
    int a=10;
    int b=10;
    std::thread tid1(myFunc1,a,b);
    std::thread tid2(myFunc2,&a,std::ref(b));
    tid1.join();
    tid2.join();
}

给分离(detach)的线程传递自动变量的引用或指针时,需保证在线程运行期间,变量一直有效。

int mian()
{
    {
        int a=10;
    	int b=10;
        std::thread tid2(myFunc2,&a,std::ref(b));
        //作用域结束,a,b变量销毁,tid2线程对象可能正在运行,并引用了已销毁的对象
    	tid2.detach();
    }
}

给分离(detach)的线程传递需转换的自动变量的引用或指针参数时,需在thread构造函数前进行转换。

void myFunc(string str)
{
    
}
int mian()
{
    {
         char buf[]="abcd";
    	//buf可能在被转换为string对象前退出
    	std::thread tid1(myFunc,buf);
        //在thread构造函数前对buf进行转换
    	std::thread tid1(myFunc,static<string>(buf));
   		tid1.detach();
    } 
}

三、线程间访问共享数据

  • 原子操作

    atomic模板类创建的变量的操作具有原子性,即不可分割性,仅支持一个线程同时访问。

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

atomic<int> num = 0;

void func1() {
	for (size_t i = 0; i < 10; i++)
	{
		num++;
		cout << "this pthread id: " << this_thread::get_id() << " num : " << num << endl;
	}
}
void func2() {
	for (size_t i = 0; i < 10; i++)
	{
		num++;
		cout << "this pthread id: " << this_thread::get_id() << " num : " << num << endl;
	}
}

int main()
{
	thread tid1(func1);
	thread tid2(func2);
	tid1.join();
	tid2.join();
}
  • 互斥锁

    mutex模板类创建的锁变量能够对临界区资源进行加锁,如果一个线程抢到了锁,在其未解锁时,其他线程不能访问对被锁上的临界区资源加锁。

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

mutex m;
int num = 0;
void func1() {
	for (size_t i = 0; i < 10000; i++)
	{
		m.lock();
		num++;
		cout << "this pthread id: " << this_thread::get_id() << " num1 : " 
             << num << endl;
		m.unlock();
	}
}
void func2() {
	for (size_t i = 0; i < 10000; i++)
	{
		m.lock();
		num++;
		cout << "this pthread id: " << this_thread::get_id() << " num1 : " 
             << num << endl;
		m.unlock();
	}
}

int main()
{
	thread tid1(func1);
	thread tid2(func2);
	tid1.join();
	tid2.join();
}

在使用互斥锁的时候,需要尽量避免互斥锁的相互嵌套,防止发生死锁。

mutex m1;
mutex m2;
int num1 = 0;
int num2 = 0;
int main()
{
    /*tid1和tid2抢占锁资源,假设此时tid1抢到了m1锁,tid2抢到了m2锁,然后tid1试图对m2加锁,tid2试图对	m1加锁,但此时m1和m2都处于加锁状态,所以tid1和tid2产生了死锁。*/
	thread tid1([]() {
		m1.lock();
		handle_num1();
		m2.lock();
		handle_num2();
		m1.unlock();
		m2.unlock();
		});
	thread tid2([]() {
		m2.lock();
		handle_num2();
		m1.lock();
		handle_num1();
		m1.unlock();
		m2.unlock();
		});
	tid1.join();
	tid2.join();
}
  • 自动释放锁

    ​ C++11两个锁管理类,lock_guard和unique_lock,这两个锁都基于RAII设计,即资源获取就初始化。也就是说这两个锁对象会在创建时自动加锁,释放时自动解锁

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

mutex m;
int num = 0;
void func1() {
	for (size_t i = 0; i < 10000; i++)
	{
		lock_guard<mutex> m_guaed(m);
		num++;
		cout << "this pthread id: " << this_thread::get_id() << " num1 : " 
             << num << endl;
		
	}
}
void func2() {
	for (size_t i = 0; i < 10000; i++)
	{
		lock_guard<mutex> m_guaed(m);
		num++;
		cout << "this pthread id: " << this_thread::get_id() << " num1 : " 
             << num << endl;
	}
}

int main()
{
	thread tid1(func1);
	thread tid2(func2);
	tid1.join();
	tid2.join();
}

使用锁管理类,还会防止,在异常状态下,mutex不能正常解锁的情况,例如:

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

mutex m;
int num = 0;
void func1() {
	try
	{
		for (size_t i = 0; i < 10; i++)
		{
			lock_guard<mutex> m_guaed(m);
			num++;
			if (i==3)
			{
				throw exception("异常!!!!!!!");

			}
			cout << "this pthread id: " << this_thread::get_id() << " num1 : "
				 << num << endl;

		}
	}
	catch (const std::exception& e)
	{
		cout << e.what() << endl;
	}
	
}
void func2() {
	for (size_t i = 0; i < 10; i++)
	{
		lock_guard<mutex> m_guaed(m);
		num++;
		cout << "this pthread id: " << this_thread::get_id() << " num1 : " 
             << num << endl;
	}
}

int main()
{
	thread tid1(func1);
	thread tid2(func2);
	tid1.join();
	tid2.join();
}

如果mutex也想在异常情况下能够正常继续运行,则需要在catch语句中将互斥锁解锁。

void func1() {
	try
	{
		for (size_t i = 0; i < 10; i++)
		{
			//lock_guard<mutex> m_guaed(m);
			m.lock();
			num++;
			if (i ==1)
			{
				throw exception("异常!!!!!!!");
			}
			cout << "this pthread id: " << this_thread::get_id() << " num1 : "
				<< num << endl;
			m.unlock();
		}
	}
	catch (const std::exception& e)
	{
		m.unlock();
		cout << e.what() << endl;
	}
}

unique_lock类和lock_guard功能相似,但是unique_lock独占所管理的锁对象,并且能够控制锁的粒度,也就是说lock_guard只能在lock_guard对象作用域结束后自动释放锁,而unique_lock能够手动的对所管理的锁进行解锁

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

mutex m;
int num = 0;
void func1() {
	for (size_t i = 0; i < 10; i++)
	{
		unique_lock<mutex> uLock(m);
		num++;
		if (i ==1)
		{
			throw exception("异常!!!!!!!");
		}
		cout << "this pthread id: " << this_thread::get_id() << " num1 : "
			 << num << endl;
		uLock.unlock();
		}
	}
}
void func2() {
	for (size_t i = 0; i < 10; i++)
	{
		unique_lock<mutex> uLock(m);
		num++;
		cout << "this pthread id: " << this_thread::get_id() << " num1 : " 
             << num << endl;
		uLock.unlock();
	}
}

int main()
{
	thread tid1(func1);
	thread tid2(func2);
	tid1.join();
	tid2.join();
}

unique_lock不仅支持手动解锁,同样也支持作用域结束自动解锁。

  • 条件变量
    • 条件变量不是锁
    • c++11条件变量需要和锁结合使用
    • 条件变量能够阻塞和唤醒线程
#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
using namespace std;

mutex m;
//定义一个条件变量,condition_variable仅支持unique_lock
condition_variable cv;
/*
	condition_variable_any支持所有锁类型
*/
void func1()
{
	for (size_t i = 0; i < 10; i++)
	{
		this_thread::sleep_for(chrono::seconds(1));
		cout << "唤醒一个线程: ";
        //唤醒一个线程
		cv.notify_one();
        /*
        	唤醒所有线程
        	cv.notify_all();
        */
	}
}
void func2()
{
    //使用unique_lock初始化
	unique_lock<mutex> uloc(m);
    //释放unique_lock锁,然后阻塞在当前位置
	cv.wait(uloc);
	cout << this_thread::get_id() << endl;
}
int main()
{
	thread tid1(func1);
	thread tid2[10];
	for (size_t i = 0; i < 10; i++)
	{
		tid2[i] = thread(func2);
	}
	tid1.join();
	for (size_t i = 0; i < 10; i++)
	{
		tid2[i].join();
	}

}

四、异步通信

同步和异步是消息通信的机制,阻塞和非阻塞是等待返回结果的状态。两者不可混为一谈。

  • 同步

​ 即在调用一个函数时,在没有得到返回结果前,不继续向下运行。

  • 异步

    ​ 调用一个函数,立即返回,不等待函数运行结束。

  • future

    ​ c++11中用future来从异步任务中获取结果,并通过以下函数之一进行构造。

    • async

      创建一个异步任务并返回future对象。

    #include<iostream>
    #include<future>
    using namespace std;
    
    int mFunc(int a)
    {
    	int i = a;
    	for (int j = 0; j < a; ++j) 
    	{ 
    		for (int k = 0; k < a; ++k)
    		{
    			i++;
    		}
    	}
    	chrono::seconds(1);
    	return i;
    }
    int main()
    {
    	future<int> fut = async(mFunc, 77777);
    	cout << "请等待计算结果";
    	std::chrono::seconds span1(1);
    	//每秒进行一次判断,如果函数运行未返回,则输出‘.’
    	while (fut.wait_for(span1)!=future_status::ready)
    	{
    		cout << '.' << flush;
    	}
    	int x = fut.get();		//获取async的结果
    	cout << "\n计算结果为:" << x << endl;
    	return 0;
    }
    
    • promise::get_future

      promise用来包装一个值,将数据和future绑定起来,为获取线程中的值提供便利。promise提供一个set_value操作和future的get操作对应。

    #include<iostream>
    #include<future>
    using namespace std;
    
    void mFunc(future<int>& fut)
    {
    	cout << "等待中";
    	while (fut.wait_for(chrono::milliseconds(100))==future_status::timeout)
    	{
    		cout << '.';
    	}
    	int x = fut.get();
    	cout << "\nfut.get() = " << x << endl;
    }
    
    int main()
    {
    	promise<int> pro;
    	future<int> fut = pro.get_future();
    	thread tid1(mFunc, ref(fut));
    	this_thread::sleep_for(chrono::seconds(1));
    	pro.set_value(10);
    	tid1.join();
    	return 0;
    }
    
    • packaged_task::get_future

      packaged_task包装了一个可调用对象,以便异步调用

    #include<iostream>
    #include<future>
    using namespace std;
    
    
    int mFunc(int arg)
    {
    	return arg * 2;
    }
    int main()
    {
    	packaged_task<int(int)> pac(mFunc);
    	future<int> fut = pac.get_future();
    	thread tid1(move(pac), 24);
    	tid1.detach();
    	int ret = fut.get();
    	cout << ret << endl;
    	return 0;
    }