在C++11之前我们会用pthread_xxx来创建线程,相对来说是比较繁琐且不易读的。因此C++11引入了std::thread来创建线程,支持对线程join或者detach的操作,代码如下:
#include
#include
#include
#include
using namespace std;
void fun(int i ){
std::cout << i << " thread id: " << std::this_thread::get_id() << std::endl;
}
void* fun_p(void *){
std::cout << "thread id: " << std::this_thread::get_id() << std::endl;
}
int main(int argc, char** argv){
auto fun_1 = [](int k){
for(int i = 0; i < k; ++i)
std::cout << k << " thread id: " << std::this_thread::get_id() << std::endl;
sleep(1);
};
// c++11
std::thread thread_1(fun, 1);
std::thread thread_2(fun_1, 2);
//joinable 检测子线程是否可以被join
//join 等待子线程运行结束后,主线成再结束;
//detach 是将子线程的管理权限交给系统root去管理,也就是当主线程释放后,子线程可能仍在运行。
if(thread_1.joinable())
thread_1.join();
if(thread_2.joinable())
thread_2.join();
// pthread
pthread_t t1;
pthread_create(&t1, nullptr, fun_p, nullptr);
pthread_join(t1, nullptr);
return 0;
}
上面实例只是一次简单的线程创建和线程释放的操作,需要手动的创建和释放线程,这样有时候会将释放线程的操作给遗漏掉,这样子线在未执行完成的时候主线程有限结束,会产生一些不可控的程序出错。因此我们将线程的管理进行封装,如下所示:
#include
#include
#include
using namespace std;
class ThreadGuard{
public:
enum class DesAction{ join, detach };
ThreadGuard(std::thread t, DesAction a)
:t_(std::move(t)), action_(a){
}
~ThreadGuard(){
if(t_.joinable() && (action_ == DesAction::join)){
//get_id 获取子线程的线程号
std::cout << "wait join thread: " << t_.get_id() << std::endl;
t_.join();
std::cout << "joined thread" << std::endl;
} else {
t_.detach();
}
}
std::thread& get() { return t_; }
ThreadGuard(ThreadGuard&&) = default;
ThreadGuard& operator=(ThreadGuard&&) = default;
private:
std::thread t_;
DesAction action_;
}; //ThreadGuard
int main(int argc, char** argv){
auto fun_1 = [](int k){
for(int i = 0; i < k; ++i)
std::cout << k << " thread id: " << std::this_thread::get_id() << std::endl;
sleep(1);
};
ThreadGuard t(std::thread(fun_1, 3), ThreadGuard::DesAction::join);
return 0;
}
thread除了以上的基本功能外,还有额外的一些工功能,例如: 获取cpu个数、线程休眠等,如下所示:
//获取cpu的个数
std::cout << "cpu number: " << t_.hardware_concurrency() << std::endl;
//handle用于thread的相关处理
t_.native_handle();
// 线程休眠
std::this_thread::sleep_for(std::chrono::seconds(1));
std::mutex是一种线程同步的手段,用于保存多线程同时操作的共享资源。mutex分为四种,如下:
std::mutex:独占的互斥量,不能地递归使用,不带超时功能;
std::recurise_mutex:递归互斥量,可重入,不带超时功能;
std::timed_mutex:带超时功能,不能递归;
std::recurise_timed_mutex:带递归、带超时功能。
//std::mutex
std::mutex mutex_;
int k = 3;
int mutex_test(){
auto fun_1 = [](){
mutex_.lock();
for(int i = 0; i < k; ++i){
std::cout << k << " thread id: " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
mutex_.unlock();
};
std::thread t[5];
for(int i = 0; i < 5; i++)
t[i] = std::thread(fun_1);
std::cout << "*********************" << std::endl;
for(int i = 0; i < 5; ++i){
if (t[i].joinable())
t[i].join();
}
std::cout << "Successful" << std::endl;
return 0;
}
//std::timed_mutex
std::timed_mutex timed_mutex_;
int timed_mutex_test(){
auto fun_1 = [](){
timed_mutex_.try_lock_for(std::chrono::milliseconds(2000));
for(int i = 0; i < k; ++i){
std::cout << k << " thread id: " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(10));
}
timed_mutex_.unlock();
};
std::thread t[2];
for(int i = 0; i < 2; i++)
t[i] = std::thread(fun_1);
for(int i = 0; i < 2; ++i){
if (t[i].joinable())
t[i].join();
}
std::cout << "Successful" << std::endl;
return 0;
}
在这里主要介绍两种RAII方式的锁封装,可以动态的释放锁资源,防止线程由于编码失误导致一直持有锁。C++11主要有std::unique_lock和std::lock_guard两种方式,使用方式都比较类似,如下所示:
std::mutex mutex_;
int k = 3;
int lock_test(){
auto fun_1 = [](){
std::unique_lock<std::mutex> lock_(mutex_);
//std::lock_guard lock(mutex_);
for(int i = 0; i < k; ++i){
std::cout << k << " thread id: " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
};
std::thread t[2];
for(int i = 0; i < 2; i++)
t[i] = std::thread(fun_1);
for(int i = 0; i < 2; ++i){
if (t[i].joinable())
t[i].join();
}
std::cout << "Successful" << std::endl;
return 0;
}
std::lock_guard相比于std::unique_lock更加轻量级,少了一些成员函数,std::unique_lock类有unlock函数,可以手动释放锁,所以条件变量都配合std::unique_lock使用,而不是std::lock_guard,因为条件变量在wait时需要手动释放锁的能力,关于std::condition_variable,后面会讲到。
C++11提供了原子类型的std::atomic,理论上这个T可以是任意类型,但是在平时只存放整形,其余的均每怎么用过,整形有这种源自变量已经足够方便,就不需要使用std::mutex来保护变量了。如下所示:
#include
#include
//old counter
class Counter{
private:
int count;
std::mutex mutex_;
public:
Counter()
:count(0){
}
void add(){
std::lock_guard<std::mutex> lock_(mutex_);
++count;
}
void sub(){
std::lock_guard<std::mutex> lock_(mutex_);
--count;
}
int get(){
std::lock_guard<std::mutex> lock_(mutex_);
return count;
}
};
//new counter
class NewCounter{
private:
std::atomic<int> count;
std::mutex mutex_;
public:
NewCounter()
:count(0){
}
void add(){
++count;
}
void sub(){
--count;
}
int get(){
return count;
}
};
C++11提供了std::call_once来保证某函数在多线程环境中只调用一次,它需要配合std::once_flag使用,代码如下:
std::once_flag once_flag_;
int call_once_test(){
auto fun = [](){
std::call_once(once_flag_, [](){
std::cout << "Hello World" << ", thread id: " << std::this_thread::get_id() << std::endl;
});
std::cout << "thread id: " << std::this_thread::get_id() << std::endl;
};
std::thread t[3];
for(int i = 0; i < 3; ++i){
t[i] = std::thread(fun);
}
for(auto& it : t){
if (it.joinable())
it.join();
}
}
条件变量是C++11引入的一种同步机制,它可以阻塞一个或多个线程,知道有线程通知或者超时时,才会唤醒正在阻塞的线程。条件变量一般配合锁来使用。
class TestConditionVar{
explicit TestConditionVar(uint32_t count)
:count_(count){
}
void CountDown(){
std::unique_lock<std::mutex> lock(mutex_);
count_--;
if( 0 == count_ ){
cv_.notify_all();
}
}
void Await(uint32_t time_ms = 0){
std::unique_lock<std::mutex> lock(mutex_);
while ( count_ > 0 ){
if ( time_ms > 0 ){
cv_.wait_for(lock, std::chrono::milliseconds(time_ms));
} else {
cv_.wait(lock);
}
}
std::cout << "Await end..." <<std::endl;
}
private:
std::condition_variable cv_;
std::mutex mutex_;
int count_;
};
C++11提供了异步操作类,主要有std::future、std::promise和std::package_task,std::future比std::thread高级一些,它作为异步结果的传输通道,通过get()可以很方便的获取线程函数的返回值;std::promise用来包装一个值,将数据和future绑定起来;而std::package_task用来包装一个调用对象,将函数和future绑定起来,方便异步调用。future是不可以复制的,若需要复制到容器中,需要使用std::shared_future。
#include
#include
#include
//std::future std::promise
void fun_fut(std::future<std::string>& fu_i){
std::string x = fu_i.get();
std::cout << "value: " << x << std::endl;
}
void future_test(){
std::promise<std::string> pro;
std::future<std::string> fut = pro.get_future();
std::thread t(fun_fut, std::ref(fut));
pro.set_value("Hello World");
if (t.joinable())
t.join();
}
//std::future std::packaged_task
std::string fun_fut_2(std::string fu_i){
return fu_i + ", Love you";
}
void future_test_2(){
std::packaged_task<std::string(std::string)> task(fun_fut_2);
std::future<std::string> fut = task.get_future();
std::thread(std::move(task), "Hello World").detach();
std::cout << "value: " << fut.get() << std::endl;
}
std::future用于访问异步操作的结果,而std::packaged_task和std::promise在future高一层,它们内部均有一个future,promise包装的是一个值,package_task包装的一个函数,当需要获取线程中的某个值,可以使用promise,当需要获取线程函数返回值,可以使用package_task。
async是比future、package_task、promise更高级的操作,它是基于任务的异步操作,通过async可以直接创建异步的任务,返回的结果会保存在future中,不需要package_task和promise那么复杂,关于线程操作应该优先使用async。
#include
#include
#include
int asnyc_fun(int x, int y){
sleep(10);
return x * y;
}
int asnyc_test(){
auto fun = [](int x, int y){ return x * y; };
auto res_ = std::async(fun, 5, 2);
std::cout << res_.get() << std::endl;
auto res = std::async(asnyc_fun, 5, 3);
std::cout << "waiting ..." << std::endl;
//当asnyc_fun函数执行结束后,才会执行下面语句
std::cout << res.get() << std::endl;
return 0;
}