while(true) {
if(pool.try_get_work()) {
// do work
}
else {
std::this_thread::yield(); // other threads can push work to the queue now
}
}
std::this_thread::yield() 的目的是避免一个线程(that should be used in a case where you are in a busy waiting state)频繁与其他线程争抢CPU时间片, 从而导致多线程处理性能下降.
std::this_thread::yield() 是让当前线程让渡出自己的CPU时间片(给其他线程使用)
std::this_thread::sleep_for() 是让当前休眠”指定的一段”时间.
sleep_for()也可以起到 std::this_thread::yield()相似的作用, (即:当前线程在休眠期间, 自然不会与其他线程争抢CPU时间片)但两者的使用目的是大不相同的:
std::this_thread::yield() 是让线程让渡出自己的CPU时间片(给其他线程使用)
sleep_for() 是线程根据某种需要, 需要等待若干时间.
一、线程库(thread)
C++11中线程较轻量级,线程的创建和执行的消耗较小,但是同时C++中线程没有明确的状态信息,如果不怕麻烦可以直接使用linux下的posix线程库,这对底层开发者还是很有用的。
**c++标准线程库是c++11/14/17才支持的,很多编译器的版本还并没有完全支持他们的标准
c++11线程特点:
1. thread创建后,C++始终尝试在一个新线程中执行任务,如果失败返回std::system_error
2. 没有线程运行结果的接口,无法获得执行结果。(后面会讨论其他方法)
3. 如果有异常在thread中发生,并且没有被捕获,std::terminate将会被调用
4. 只能调用join去结束线程,或者detach去转成后台线程执行。否则当thread被析构时,程序会被abort。
5. 如果线程detach并且main函数执行结束,所有的detach线程会被终止.
6. 线程只可以通过std::move转移,不可以复制。
1. 通过全局函数创建线程
线程类的构造函数是变参构造函数,第一个参数是线程函数,后面的参数为线程函数的参数(参数通过值传递方式,若要引用传递须加std::ref())。
#include <thread>
#include <iostream>
void hello(int i)
{
std::cout << "Hello from thread :" << i<<std::endl;
}
int main()
{
std::thread t1(hello,1);
t1.join();
return 0;
}
2. 通过仿函数创建线程
//class Counter 实现 operator()
//1) thread t1{Counter(1, 20)}; //c++统一推荐方法
//2) Counter c(1, 20);
// thread t2(c);
//3) thread t3(Counter(1,20));
#include <iostream>
#include <thread>
#include <chrono>
using namespace std;
class thread_c
{
public:
void operator () ()
{
cout << "hello from class member function" << endl;
}
private:
};
int main()
{
thread_c c;
thread t(c);
//thread t1{thread_c()};
this_thread::sleep_for(chrono::seconds(1));
return 0;
}
thread t3(Counter());是不行的,因为编译器会认为你在声明一个函数,函数名为t3,此时只能用第一种构造方式。
3. 通过lambda表达式创建线程
#include <thread>
#include <iostream>
#include <vector>
int main()
{
std::vector<std::thread> threads;
for(int i = 0; i < 5; ++i)
{
threads.push_back(std::thread([]()
{
std::cout << std::this_thread::get_id() << std::endl;
}
));
}
for(auto& thread : threads)
{
thread.join();
}
return 0;
}
4. 通过成员函数创建线程
一般常见的是一个类自己创建一个后台处理线程:thread t{&Counter::process, this};
class thread_c
{
public:
void thread_1(int a)
{
std::cout <<a<< std::endl;
}
};
int main()
{
thread_c b;
std::thread t1(&thread_c::thread_1, b,1);
t1.join();
std::this_thread::sleep_for(std::chrono::seconds(2));
return 0;
}
线程本地存储 thread_local
thread_local int n;
n作为线程参数传递给线程,那么每个线程有一个n的副本,在线程整个生命周期中存在,且只初始化一次,如同static局部变量.
互斥体类
1) 非定时互斥体类
std::mutex std::recursive_mutex
方法:
lock() : 尝试获取锁,并且阻塞直到获取锁。
try_lock() : 尝试获取锁,并立即返回,成功获取返回true,否则false。
unlock() : 释放锁。
mutex与recursive_mutex的区别在于,前者获得锁后不能再获取,这会死锁,后者能递归获取,注意释放次数应与获取次数相等。
2)定时互斥锁类
std::timed_mutex std::recursive_timed_mutex
方法:
lock() , try_lock() , unlock()
try_lock_for(rel_time) : 指定相对时间内获得返回true, 超时返回false。
try_lock_until(abs_time) : 指定系统绝对时间内获得返回true, 超时返回false。
timed_mutex与recursive_timed_mutex区别同上。
timed_mutex mtx;
while (!mtx.try_lock_for(std::chrono::milliseconds(200))
2. 锁类
锁类是一个包装器,析构函数会自动释放关联的互斥体。使用C++的RAII(Res Acquisition is init 资源获取即初始化),利用destructor做unlock处理。其构造函数会要求获得互斥体,并阻塞直到获得锁。
1) 简单锁 std::lock_guard
std::mutex mutex;
{
std::lock_guard<std::mutex> lg(mutex);
}
2) 复杂锁 std::unique_lock
explict unique_lock( mutex_type& m); //阻塞直到获得锁。
unique_lock(mutex_type& m, defer_lock_t) noexcept; //保存一个互斥体引用,不会立即尝试获得锁。锁可以在以后获得。之后可以用std::lock() 进行加锁
unique_lock(mutex_type& m, try_to_lock_t); //尝试获得引用的互斥锁,未能获得也不阻塞。
unique_lock(mutex_type& m, adopt_lock_t); //该锁假定线程已获得引用的互斥锁,并负责管理这个锁。
template<class Clock, class Duration>
unique_lock(mutex& m, const chrono::time_point<Clock, Duration>& abs_time); //尝试获取该锁,直到超过给定的绝对时间。
template<class Rep, class Period>
unique_lock(mutex& m, const chrono::duration<Rep, Period>& rel_time); //尝试获取该锁,直到超过给定的相对时间。 unique_lock类还支持lock(), try_lock(), try_lock_for(), try_lock_until()等方法。 通过owns_lock()查看是否获得了这个锁;也可以用if对unique_lock对象直接判断是否获得锁,因为它定义了bool()运算符。
四、条件变量
1. condition_variable
只能等待unique_lock<mutex>的条件变量
notify_one(); //唤醒等待这个条件变量的线程之一
notify_all(); //唤醒所有等待这个条件变量的线程
// 1)前提是已经获得lk的锁
// 2)调用wait会unlock lk,然后等待
// 3)当被唤醒后 lock lk
以下是重载版本,非重载版本没有pred
void wait (unique_lock<mutex>& lck, Predicate pred);
wait_for (unique_lock<mutex>& lck,
const chrono::duration<Rep,Period>& rel_time, std::move(pred));
wait_until (lck, chrono::system_clock::now() + rel_time, std::move(pred));
另外,wait_for 的最后一个参数 pred 表示 wait_for 的预测条件,只有当 pred 条件为 false 时调用 wait() 才会阻塞当前线程,并且在收到其他线程的通知后只有当 pred 为 true 时才会被解除阻塞,因此相当于如下代码:(超时返回cv_status::timeout)
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
#include <condition_variable>
using namespace std;
condition_variable cv;
mutex mtx;
void thread_1()
{
unique_lock<mutex> ulock(mtx);
cv.wait(ulock);
cout << "hello from thread_1" << endl;
}
int main(int argc, char **argv)
{
thread t1(thread_1);
this_thread::sleep_for(chrono::seconds(1));
cv.notify_one();
t1.join();
return 0;
}
2.condition_variable_any
// condition_variable_any::wait (with predicate)
#include <iostream> // std::cout
#include <thread> // std::thread, std::this_thread::yield
#include <mutex> // std::mutex
#include <condition_variable> // std::condition_variable_any
std::mutex mtx;
std::condition_variable_any cv;
int cargo = 0;
bool shipment_available() {return cargo!=0;}
void consume (int n)
{
for (int i=0; i<n; ++i)
{
mtx.lock();
cv.wait(mtx,shipment_available);
// consume:
std::cout << cargo << '\n';
cargo=0;
mtx.unlock();
}
}
int main ()
{
std::thread consumer_thread (consume,10);
// produce 10 items when needed:
for (int i=0; i<10; ++i)
{
while (shipment_available())
std::this_thread::yield();
mtx.lock();
cargo = i+1;
cv.notify_one();
mtx.unlock();
}
consumer_thread.join();
return 0;
}
五、信号量类
很遗憾c++并没有提供信号量( 信号灯)机制的库。所以可以自己设计一个,下面的程序是google的,只供参考。
#include<mutex>
#include<condition_variable>
class CSemaphore
{
private:
std::condition_variable cv;
std::mutex mutex;
int value;
public:
CSemaphore(int init) :value(init)
{
}
void wait()
{//类似P操作
std::unique_lock<std::mutex> lock(mutex);
while (value < 1)
{
cv.wait(lock);
}
value--;
}
bool try_wait()
{
std::unique_lock<std::mutex> lock(mutex);
if (value < 1)
return false;
value--;
return true;
}
void post()
{//类似V操作
{
std::unique_lock<std::mutex> lock(mutex);
value++;
}
cv.notify_one();
}
};
六、线程异步调用和返回值
1.std::async
异步调用一个callable的对象,但是不保证调用时机。可以通过传入std::launch::async/deferred来制定是否立即调用和延迟(由系统决定调用时机)。async不保证内部使用线程池来实现,但是肯定使用一个后台线程执行任务。
2.std::future
类似于Java的future,获得任务执行结果,对于无返回值的结果使用future,如果调用future的get方法,可以保证async的会立即执行。async()方法返回值是future.
int sum(int a,int b){return a+b;}
//如果async的返回值没有被使用,那么async将一直阻塞
//编译器可以优化为 std::async(sum,1,1).get(); 阻塞调用
std::future f = std::async(sum,1,1);
int result = f.get();//获得结果,如果有异常还会抛出异常
//f.wait() 等运行完成
//f.wait_for(dur) 等待一定时间或运行完成
//f.wait_until(fp) 等待某一时刻或运行完成
//f.share() 生成一个shared_future 见下文
3.std::shared_future
类似于future,但是future的get方法只能被调用一次,二次调用可能出现未定义的行为(不一定抛出异常或出错)。shared_future的二次调用可以保证一样,并且如有异常,可以保证异常一样。
std::shared_future f = std::async(sum,1,1).share();//需要使用share来获得。
4.std::packaged_task
类似于async,但是可以自己控制调用时机,也可以使用线程去调用任务执行。
std::packaged_task<int(int,int)> task(sum);//封装函数
std::future<int> f = task.get_future();//获得future
task(1,1); //直接调用
//std::thread t{std::move(task),5,5}; //线程方式调用
//t.join();//need join or detach thread
int result = f.get();//获得结果
// packaged_task example
#include <iostream> // std::cout
#include <future> // std::packaged_task, std::future
#include <chrono> // std::chrono::seconds
#include <thread> // std::thread, std::this_thread::sleep_for
// count down taking a second for each value:
int countdown (int from, int to)
{
for (int i=from; i!=to; --i)
{
std::cout << i << '\n';
std::this_thread::sleep_for(std::chrono::seconds(1));
}
std::cout << "Lift off!\n";
return from-to;
}
int main ()
{
std::packaged_task<int(int,int)> tsk (countdown); // set up packaged_task
std::future<int> ret = tsk.get_future(); // get future
std::thread th (std::move(tsk),10,0); // spawn thread to count down from 10 to 0
// ...
int value = ret.get(); // wait for the task to finish and get result
std::cout << "The countdown lasted for " << value << " seconds.\n";
th.join();
return 0;
}
4. call_once 和once_flag
call_once是c++11中引入的新特性,用于保证某个函数只调用一次,即使是多线程环境下,它也可以可靠地完成一次函数调用。特别适用于某个初始化只执行一次的场景。
- 若调用call_once一切顺利,将会翻转once_flag变量的内部状态,再次调用该函数时,所对应的目标函数不会被执行。
- 若调用call_once中发生异常,不会翻转once_flag变量的内部状态,再次调用该函数时,目标函数仍然尝试执行。
struct once_flag
{
once_flag() _NOEXCEPT : __state_(0) {}
private:
once_flag(const once_flag&); // = delete;
once_flag& operator=(const once_flag&); // = delete;
unsigned long __state_;
template<class _Callable>
friend void call_once(once_flag&, _Callable);
};
template<class Callable, class ...Args>
void call_once(once_flag& flag, Callable&& func, Args&&... args);
第一个参数是std::once_flag的对象(once_flag是不允许修改的,其拷贝构造函数和operator=函数都声明为delete),第二个参数可调用实体,即要求只执行一次的代码,后面可变参数是其参数列表。
call_once保证函数fn只被执行一次,如果有多个线程同时执行函数fn调用,则只有一个活动线程(active call)会执行函数,其他的线程在这个线程执行返回之前会处于”passive execution”(被动执行状态)——不会直接返回,直到活动线程对fn调用结束才返回。对于所有调用函数fn的并发线程,数据可见性都是同步的(一致的)。
如果活动线程在执行fn时抛出异常,则会从处于”passive execution”状态的线程中挑一个线程成为活动线程继续执行fn,依此类推。一旦活动线程返回,所有”passive execution”状态的线程也返回,不会成为活动线程。
#include<iostream>
#include<mutex>
//在单例模式中建立对象,最好使用call_once或者进行加锁
class singleton
{
public:
static singleton* instance()
{
std::call_once(_flag, [&](){_instance = new singleton();});
return _instance;
}
private:
static std::once_flag _flag;
static singleton* _instance;
singleton() {}
~singleton() {}
};
singleton* singleton::_instance = nullptr;
std::once_flag singleton::_flag;
int main(int argc, char* argv[])
{
std::cout<<singleton::instance()<<std::endl;
std::cout<<singleton::instance()<<std::endl;
std::cout<<singleton::instance()<<std::endl;
return 0;
}
注意: once_flag的生命周期,它必须要比使用它的线程的生命周期要长。所以通常定义成全局变量比较好。
原子操作库(atomic)
atomic<int> counter(0); //等效于 atomic_int counter(0);
#include<atomic>
#include<thread>
#include<iostream>
void func(std::atomic<int>& counter)
{
for(int i=0; i<1000; ++i)
++counter;
}
int main()
{
std::atomic<int> counter(0);
std::vector<std::thread> threads;
for(int i=0; i<10; ++i)
threads.push_back(std::thread{func, std::ref(counter)});
for(auto& t : threads)
t.join();
std::cout<<"Result="<<counter<<std::endl;
return 0;
}
七、this_thread
this_thread::get_id: 返回当前线程的id
this_thread::yield: 让调度器先运行其它的线程,这在忙于等待状态时很有用
this_thread::sleep_for: 将当前线程置于阻塞状态,时间不少于参数所指定的时间段
this_thread::sleep_util: 在指定的时刻来临前,一直将当前的线程置于阻塞状态