关于c++11 多线程的一些小结, 内容基本上是自己看书的笔记。 详细内容还需参考cppreference.com
过程中参考了这篇博文,和 这篇博文
async 在后台启动一个异步执行的任务 通常和 future 一起使用
std::async(launch, function, arg ...)
launch
发射策略function
是一个可调用对象, 如函数, lambda, 成员函数arg
传递给可调用对象的参数当传递给std::async()
一个成员函数时, 后面必须传递一个该成员函数所在类的一个对象:
class X
{
public:
void mem(int num);
};
...
X x;
auto a = std::async(&X::mem, x, 2);
如果传给std::async()
的可调用对象没有返回值, std::future::get()
也没有返回值
发射策略std::launch::async
和 std::launch::deferred
:
std::async(std::launch::async, function)
立即执行异步任务
std::async(std::launch::deferred, function)
延缓异步调用,直到std::future::get()
或者std::future::wait()
如果没有指定发射策略, 系统有可能立即执行异步任务, 有可能推迟。。。(测试了几个程序都是延迟到程序快退出时才执行)
std::future
将某一操作的结果存入一个 future对象中
在获取多线程异步操作结果中使用较多
std::future::get()
获取某一操作的结果(此结果可能是一个异常), 如果该操作还没出结果则阻塞
std::future
将一个异步执行的结果存入result
如果没有对返回的future调用get
或wait
, function可能永不被调用(假设没有指定发射策略为async)
对std::future
只能调用 get()
一次, 之后只能用 future::valid()
来检查, 其他调用会产生未定义行为
future::valid()
用来检查future是否有效,即结果是否已准备好
在执行异步任务发生异常时,异常不会马上抛出,而时等到get()
调用时抛出
用例:
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int doSomethingWithExcep()
{
//一直向链表中添加元素 直到栈用完抛出异常
list<int> li;
while(true)
{
for (int i = 0; i < 1000000; ++i)
{
li.push_back(i);
}
cout.put('.').flush();
}
}
int doSomething( char c)
{
//设置随机引擎和随机分布
std::default_random_engine dre(c);
std::uniform_int_distribution<int> id(10, 1000);
for (int i = 0; i < 10; ++i)
{
//每次等待随机时间 然后打印字符
std::this_thread::sleep_for(chrono::milliseconds(id(dre)));
cout.put(c).flush();
}
return c;
}
int func1()
{
return doSomething('/');
}
int func2()
{
return doSomething('+');
}
int main1()
{
std::cout << "starting func1() in background"
<< " and func2 in foreground:" << std::endl;
std::future<int> result1(std::async(std::launch::async, func1));
int result2 = func2();
int result = result1.get() + result2;
std::cout << "\nresult of func1() + func2(): " << result << std::endl;
std::cout << "waiting for exception ....\n" << std::endl;
auto f1 = std::async(doSomethingWithExcep);
cin.get();
try{
f1.get(); //在线程中发生了异常, get() 时抛出
}
catch(const exception& e)
{
std::cerr << "EXCEPTION: " << e.what() << std::endl;
}
return 0;
}
int main()
{
std::cout << "starting 2 operation..\n";
auto f1 = async(std::launch::async, []{doSomething('.');});
auto f2 = async(std::launch::async,[]{doSomething('+');});
//确保至少有一个任务已经启动
if (f1.wait_for(chrono::seconds(0)) != future_status::deferred ||
f2.wait_for(chrono::seconds(0)) != future_status::deferred)
{
//轮询两个任务是否完成 若没有 if 判断 while 可能一直循环下去
while( f1.wait_for(chrono::seconds(0)) != future_status::ready &&
f2.wait_for(chrono::seconds(0)) != future_status::ready)
{
this_thread::yield();
}
}
cout.put('\n').flush();
try{
//不加get 异步任务有可能在主线程结束之前还没有执行完
f1.get();
f2.get();
}
catch (const exception& e){
cout << "\nEXCEPTION: " << e.what() << endl;
}
cout << "\ndone" << endl;
return 0;
}
std::thread(function, arg1, arg2...)
立即创建新的线程, 失败时抛出std::system_error
异常和错误码
利用thread创建的线程如果发生异常而未捕捉, 程序会立刻中止, 并调用std::terminate()
thread 的 join 和 detach 方法作用与 pthread 设置线程属性的函数相似
thread::joinable()
如果一个线程时 joinable
的 返回 true
thread::join()
等待线程退出, 如果对一个不是 joinable
的线程调用此函数, 抛出异常thread::detach()
将线程设为可分离状态对于 detach 的线程, 其有可能在主程序退出时, 其可能还在运行, 并且访问已经被析构的全局或静态变量(有点疑问 主程序退出了 创建的线程也退出了啊。。)
因此以引用方式传递变量和对象给线程带有极大的风险, 建议传值
另外, 如果调用 quick_exit()
结束程序并不会销毁全局或者静态变量
获取线程ID std::this_thread::get_id()
也可以用一个变量 std::thread::id threadid
保存线程ID
#include
#include
#include
#include
#include
#include
using namespace std;
void doSomething( int num, char c)
{
try{
std::default_random_engine dre(c);
std::uniform_int_distribution<int> id(10, 1000);
for (int i = 0; i < num; ++i)
{
//每次等待随机时间 然后打印字符
std::this_thread::sleep_for(chrono::milliseconds(id(dre)));
cout.put(c).flush();
}
std::cout << "\nthread " << std::this_thread::get_id()
<< " exit..." << std::endl;
}
catch (const exception& e)
{
cerr<< "THREAD-EXCEPTION (thread " << this_thread::get_id() << ") "<< e.what() << endl;
}
catch (...){
cerr<< "THREAD-EXCEPTION (thread " << this_thread::get_id() << ") "<< endl;
}
}
int main()
{
try
{
thread t1(doSomething, 5, '.');
cout << "- started fg thread " << t1.get_id() << endl;
for (int i = 0; i < 5; ++i)
{
thread t(doSomething, 10, 'a'+i);
cout << "- detach started bg thread " << t.get_id() << endl;
t.detach(); //设置5个可分离线程
}
cin.get();
cout << "- join fg thread " << t1.get_id() << endl;
t1.join();
}
catch (const exception &e){
cerr << "EXCEPTION: "<< e.what() << endl;
}
//pthread_exit((void*)0); //只结束线程, 不结束进程
return 0;
}
输出1:
started fg thread 140056933353216
detach started bg thread 140056924960512
detach started bg thread 140056916567808
detach started bg thread 140056908175104
detach started bg thread 140056899782400
detach started bg thread 140056891389696
.abcdec.deeda.cb.ebac.adedebcbaabecdabecdcbceaebcdabdad
join fg thread 140056933353216
输出2:
started fg thread 140041721177856
detach started bg thread 140041712785152
detach started bg thread 140041704392448
detach started bg thread 140041695999744
detach started bg thread 140041687607040
detach started bg thread 140041679214336
.abcdec.de #在此按下回车
join fg thread 140041721177856
eda.cb.ebac.%
#此时主线程先于detach线程退出, detach线程成了僵尸线程
#如果 在main()函数返回之前加上 pthread_exit((void*)0); 只让线程结束而不是结束整个进程, 则5个detach线程就能完整执行
std::promise
用来存储一个在稍后异步访问的值或者异常, 常用来在两个线程间通信
promise 可以配合 thread 传递值或者异常
void thr_fun(std::promise<std::string>& p)
{
try{
//do somthing...
p.set_value(somevalue); //存储值
//do somthing...
}
catch(...){
p.set_exception(std::current_exception());//存储异常
}
}
int main()
{
try{
std::promise<std::string> p;
std::thread t(thr_fun, std::ref(p));
t.detach();
//do somthing...
std::future<std::string> f(p.get_future());
f.get(); //获取结果 或者
//do somthing...
}
catch(const std::exception& e) //获取异常
{
//do somthing...
}
}
对于promise, get_future()
操作只能调用一次, 再次调用会抛出异常
std::mutex mtx;
加锁解锁:mtx.lock();
mtx.unlock();
std::lock_guard
用一个未加锁的mtx初始化, 并锁定, 或者
std::lock_guard
将一个已经锁定的mtx过继给 std::lock_guard
后者在作用域结束时自动释放锁。
lock_guard
只能在初始化时进行加锁
std::try_lock(m1, m2...)
尝试对多个锁加锁
std::mutex m1;
std::mutex m2;
int idx = std::try_lock(m1, m2); //尝试对两个锁加锁
if (idx < 0){ //两个锁加锁成功
std::lock_guard<std::mutex> lockM1(m1, std::adopt_lock);
std::lock_guard<std::mutex> lockM2(m2, std::adopt_lock);
...
}//作用域结束,自动释放锁
else{
//idx返回加锁失败的锁的序号, 以0作为起始序号
std::cerr << "could not lock mutex m" << idx+1 << std::endl;
}
在同一个线程中对一个锁再次加锁会造成死锁(包括try_lock()
), 此时可使用递归锁
std::unique_lock<>
与 std::lock_guard<>
拥有相同的接口的同时,又允许对一个锁指定 如果加锁和解锁,以及何时加锁。
std::unique_lcok lock(mutex, std::try_to_lock);
std::unique_lcok lock(mutex,std::chrono::seconds(1));
在某个时间后加锁std::unique_lcok lock(mutex, std::defer_lock);
初始化锁, 但暂时不加锁头文件
提供了两种条件变量类: condition_variable
和condition_variable_any
std::condition_variable
被设置用来唤醒一个或所有等待的线程
由于wait()
过程中会有隐式加锁解锁操作, condition_variable
所有对锁的操作都是基于 unique_lcok
。
在wait过程中会指向下面三个原子步骤:
1. 解锁 mutex 然后进入 wait 状态
2. 解除 wait 状态
3. 再次锁住 mutex
比较重要的成员函数([]代表参数可选) :
notify_one()
唤醒一个线程
notify_all()
唤醒全部等待的线程
wait(ul, pred)
使用unique_lock
用来等待通知.
注意, 由于有可能假醒(即条件变量还未设置,而wait就返回, 至于为什么, 不清楚), 还必须提供第二个检查 pred
, 当条件变量被设置且pred
返回真时,才能进行下一步操作
wait_for(ul, duration,[pred])
等待一段时间, 或者一次唤醒后pred结果为真
wait_until(ul, timepoint, [pred])
等待直到某个时间点, 或者某次唤醒后pred为真wait_for() 和 wait_until()
的不接受 pred
的版本返回值属于一下枚举类:
std::cv_status::timeout
发生超时std::cv_status::no_timeout
发生通知其接受 pred
的版本返回值是 pred
的判断结果
condition_variable
使用例子:
一个简单的生产者-消费者模型
#include
#include
#include
#include
#include
#include
std::queue<int> queue;
std::mutex queue_mutex;
std::condition_variable queue_cond_var;
void provider(int var)
{
for (int i = 0; i < 5; ++i)
{
{
std::lock_guard<std::mutex> lg(queue_mutex);
queue.push(var++);
}
queue_cond_var.notify_one();
std::this_thread::sleep_for(std::chrono::milliseconds(var));
}
}
void consumer (int num)
{
while(true)
{
int var;
{
std::unique_lock<std::mutex> ul(queue_mutex);
queue_cond_var.wait(ul, []{return !queue.empty();});
//当队列中没有数据时 程序在此处一直阻塞
var = queue.front();
queue.pop();
}
std::cout << "consumer " << num << ": " << var << std::endl;
}
}
int main()
{
auto p1 = std::async(std::launch::async, provider, 300);
auto p2 = std::async(std::launch::async, provider, 400);
auto p3 = std::async(std::launch::async, provider, 500);
auto c1 = std::async(std::launch::async, consumer, 1);
auto c2 = std::async(std::launch::async, consumer, 2);
return 0;
}
头文件
std::atomic
声明一个对象, 此对象的相关操作都是原子的, 因而可以简化对于线程访问同步变量的处理, 不用进行加锁解锁操作。
初始化:
std::atomic
用一个常量初始化一个 atomic 对象,若使用 atomic 的默认构造函数 std::atomic
, 则后续对 atomic 对象的操作只允许: std::atomic_init(&read, num);
操作:
store()
赋新值load()
取当前值的一个拷贝另外还支持运算符操作,如: + - = -= 等等
#include
#include
#include
#include
#include
long data;
std::atomic<bool> readyFlag(false);
void provider(){
std::cin.get();
data = 42; //provide some data
readyFlag.store(true);
}
void comsumer()
{
while(!readyFlag.load()){
std::cout.put('.').flush();
std::this_thread::sleep_for(std::chrono::milliseconds(500)); //等待一小段时间
}
//数据准备好了
std::cout << "\nvalue: " << data << std::endl;
}
int main()
{
//auto解析为 std::future
auto p = std::async(std::launch::async, provider);
auto c = std::async(std::launch::async, comsumer);
return 0;
}