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