【C++】C++11多线程
(1)std::thread
在C++11之前,C++语言层面是不支持多线程的,想利用C++实现并发程序,借助操作系统的API实现跨平台的并发程序存在着诸多不便。在C++11中,终于提供了多线程的标准库,提供了管理线程、保护共享数据、线程间同步操作、原子操作等类,编写跨平台的多线程代码就方便了许多。
C++11提供的std::thread
在开发多线程方面带来了便捷。
#include <iostream> #include <thread> void threadfunc() { std::cout << "thread func" << std::endl; } int main() { std::thread t1(threadfunc); t1.join(); //等待threadfunc运行结束 return 0; }
首先定义线程对象t1
,线程函数threadfunc
运行在线程对象t1
中,当线程创建成功并执行线程函数后,一定要保证线程函数运行结束才能退出,这里调用了join()
函数阻塞线程,直到threadfunc()
运行结束,回收对应创建线程的资源。如果不阻塞线程,就不能保证线程对象t1
在threadfunc()
运行期间有效,下面不调用join()
阻塞线程。
#include <iostream> #include <thread> void threadfunc() { std::cout << "thread func" << std::endl; } int main() { std::thread t1(threadfunc); //t1.join(); //等待threadfunc运行结束 return 0; }
在运行时会引起程序崩溃。
除了调用join()
阻塞线程,保证线程对象在线程函数运行期间的有效性,还可以通过线程分离的手段实现,调用detach()
函数使得线程对象与线程函数分离,这样,在线程函数运行期间,线程对象与线程函数就没有联系了,此时的线程是作为后台线程去执行,detach()
后就无法再和线程发生联系,也不能通过join()
来等待线程执行完毕,线程何时执行完无法控制,它的资源会被init
进程回收,所以,通常不采用detach()
方法。
#include <iostream> #include <thread> void threadfunc() { std::cout << " detach thread func" << std::endl; } int main() { std::thread t1(threadfunc); t1.detach(); //线程分离 return 0; }
这里调用detach()
实现线程分离,但是运行后,主线程退出的时候threadfunc()
还没有输出“detach thread func”
,threadfunc()
什么时候运行结束也无法确定,为了看到所创建的线程运行结果,在主线程等待一下再退出。
#include <iostream> #include <thread> #include <chrono> //时间 void threadfunc() { std::cout << "detach thread func" << std::endl; } int main() { std::thread t1(threadfunc); t1.detach(); while (true) { std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒 break; } return 0; }
此时运行结果:
detach thread func
通过std::thread
创建的线程是不可以复制的,但是可以移动。
#include <iostream> #include <thread> #include <chrono> void threadfunc() { std::cout << "move thread func" << std::endl; } int main() { std::thread t1(threadfunc); std::thread t2(std::move(t1)); t2.join(); while (true) { std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒 break; } return 0; }
输出结果:
move thread func
移动后t1
就不代表任何线程了,t2
对象代表着线程threadfunc()
。另外,还可以通过std::bind
来创建线程函数。
#include <iostream> #include <thread> #include <chrono> //时间 #include <functional> //std::bind class A { public: void threadfunc() { std::cout << "bind thread func" << std::endl; } }; int main() { A a; std::thread t1(std::bind(&A::threadfunc,&a)); t1.join(); while (true) { std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒 break; } return 0; }
创建一个类A
,然后再main
函数中将类A中的成员函数绑定到线程对象t1
上,运行结果:
bind thread func
每个线程都有自己的线程标识,也就是线程ID,当线程创建成功后,可以通过get_id()
来获取线程的ID。
#include <iostream> #include <thread> #include <chrono> #include <functional> class A { public: void threadfunc() { std::cout << "bind thread func" << std::endl; } }; int main() { A a; std::thread t1(std::bind(&A::threadfunc,&a)); std::cout << "main thread ID is : " << std::this_thread::get_id() << std::endl; std::cout << "t1 thread ID is : " << t1.get_id() << std::endl; t1.join(); while (true) { std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒 break; } return 0; }
std::this_thread::get_id()
获取的是当前线程的ID,t1.get_id()
获取的是所创建的t1对象中运行的线程ID,对应的ID分别为:
main thread ID is : 11932 t1 thread ID is : 12076 bind thread func
虽然get_id()
可以获取线程的ID,但是其返回类型是thread::id
,通过std::cout
可以输出线程ID,但是这样使用似乎不太方面,要是能转换为整型就好了。其实可以将得到的线程ID写入到ostreamstring
流中,转换成string
类型,再转换成整型。
#include <iostream> #include <thread> #include <chrono> #include <functional> #include <sstream> class A { public: void threadfunc() { std::cout << "bind thread func" << std::endl; } }; int main() { A a; std::thread t1(std::bind(&A::threadfunc, &a)); std::ostringstream os1; os1 << t1.get_id() << std::endl; std::string strID = os1.str(); //转换成string类型 int threadID = atoi(strID.c_str()); //转换成int类型 std::cout << "t1 thread ID is : " << threadID << std::endl; t1.join(); while (true) { std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒 break; } return 0; }
输出结果:
t1 thread ID is : 6956 bind thread func
2、std::mutex
进入多线程编程的世界,除了要牢牢掌握std::thread
使用方法,还要掌握互斥量(锁)的使用,这是一种线程同步机制,在C++11中提供了4中互斥量。
std::mutex; //非递归的互斥量
std::timed_mutex; //带超时的非递归互斥量
std::recursive_mutex; //递归互斥量
std::recursive_timed_mutex; //带超时的递归互斥量
从各种互斥量的名字可以看出其具有的特性,在实际开发中,常用就是std::mutex
,它就像是一把锁,我们需要做的就是对它进行加锁与解锁。
#include <iostream> #include <thread> #include <mutex> #include <chrono> std::mutex g_mutex; void func() { std::cout << "entry func test thread ID is : " << std::this_thread::get_id() << std::endl; std::this_thread::sleep_for(std::chrono::microseconds(1000)); std::cout << "leave func test thread ID is : " << std::this_thread::get_id() << std::endl; } int main() { std::thread t1(func); std::thread t2(func); std::thread t3(func); std::thread t4(func); std::thread t5(func); t1.join(); t2.join(); t3.join(); t4.join(); t5.join(); return 0; }
创建了5个线程,然后分别调用func()
函数,得到结果:
entry func test thread ID is : entry func test thread ID is : 19180 entry func test thread ID is : 3596 13632 entry func test thread ID is : 9520 entry func test thread ID is : 4460 leave func test thread ID is : 13632 leave func test thread ID is : 19180 leave func test thread ID is : leave func test thread ID is : 9520 3596 leave func test thread ID is : 4460
可以看出,并没有按顺序去执行线程函数,后面创建的线程并没有等待前面的线程执行完毕,导致结果混乱,下面用std::mutex
进行控制:
#include <iostream> #include <thread> #include <mutex> #include <chrono> std::mutex g_mutex; void func() { g_mutex.lock(); std::cout << "entry func test thread ID is : " << std::this_thread::get_id() << std::endl; std::this_thread::sleep_for(std::chrono::microseconds(1000)); std::cout << "leave func test thread ID is : " << std::this_thread::get_id() << std::endl; g_mutex.unlock(); } int main() { std::thread t1(func); std::thread t2(func); std::thread t3(func); std::thread t4(func); std::thread t5(func); t1.join(); t2.join(); t3.join(); t4.join(); t5.join(); return 0; }
只要线程进入func()
函数就进行加锁处理,当线程执行完毕后进行解锁,保证每个线程都能按顺序执行。
虽然通过lock()
与unlock()
可以解决线程之间的资源竞争问题,但是这里也存在不足。
func() { //加锁 执行逻辑处理; //如果该过程抛出异常导致程序退出了,就没法unlock //解锁 } int main() { ...... }
func()
中再执行逻辑处理中程序因为某些原因退出了,此时就无法unlock()
了,这样其他线程也就无法获取std::mutex
,造成死锁现象,其实在加锁之前可以通过trylock()
尝试一下能不能加锁。实际开发中,通常也不会这样写代码,而是采用lock_guard
来控制std::mutex
。
template <class _Mutex> class lock_guard { public: using mutex_type = _Mutex; explicit lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx) { _MyMutex.lock(); //构造函数加锁 } lock_guard(_Mutex& _Mtx, adopt_lock_t) : _MyMutex(_Mtx) { } ~lock_guard() noexcept { _MyMutex.unlock(); //析构函数解锁 } lock_guard(const lock_guard&) = delete; lock_guard& operator=(const lock_guard&) = delete; private: _Mutex& _MyMutex; };
lock_guard
是类模板,在其构造函数中自动给std::mutex
加锁,在退出作用域的时候自动解锁,这样就可以保证std::mutex
的正确操作,这也是RAII(获取资源便初始化)技术的体现。
#include <iostream> #include <thread> #include <mutex> #include <chrono> std::mutex g_mutex; void func() { std::lock_guard<std::mutex> lock(g_mutex); //加锁 std::cout << "entry func test thread ID is : " << std::this_thread::get_id() << std::endl; std::this_thread::sleep_for(std::chrono::microseconds(1000)); std::cout << "leave func test thread ID is : " << std::this_thread::get_id() << std::endl; //退出作用域后,lock_guard对象析构就自动解锁 } int main() { std::thread t1(func); std::thread t2(func); std::thread t3(func); std::thread t4(func); std::thread t5(func); t1.join(); t2.join(); t3.join(); t4.join(); t5.join(); return 0; }
运行结果:
entry func test thread ID is : 19164 leave func test thread ID is : 19164 entry func test thread ID is : 15124 leave func test thread ID is : 15124 entry func test thread ID is : 2816 leave func test thread ID is : 2816 entry func test thread ID is : 17584 leave func test thread ID is : 17584 entry func test thread ID is : 15792 leave func test thread ID is : 15792
3、std::condition_variable
条件变量是C++11提供的另外一种线程同步机制,通过判断条件是否满足,决定是否阻塞线程,当线程执行条件满足的时候就会唤醒阻塞的线程,常与std::mutex
配合使用,C++11提供了两种条件变量。
std::condition_variable
,配合std::unique_lock<std::mutex>
使用,通过wait()
函数阻塞线程;std::condition_variable_any
,可以和任意带有lock()
、unlock()
语义的std::mutex
搭配使用,比较灵活,但是其效率不及std::condition_variable
;
std::unique_lock
:C++11提供的std::unique_lock
是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。std::unique_lock
比std::lock_guard
使用更加灵活,功能更加强大。使用std::unique_lock
需要付出更多的时间、性能成本。
下面利用std::mutex
与std::condition_variable
实现生产者与消费者模式。
#include <iostream> #include <condition_variable> #include <thread> #include <list> #include <mutex> #include <chrono> class CTask { public: CTask(int taskID) { this->taskId = taskID; } void dotask() { std::cout << "consumer a task Id is " << taskId << std::endl; } private: int taskId; }; std::list<std::shared_ptr<CTask>> g_task; std::mutex g_mutex; std::condition_variable g_conv; //生产者线程 void ProdecerFunc() { int n_taskId = 0; std::shared_ptr<CTask> ptask = nullptr; while (true) { ptask = std::make_shared<CTask >(n_taskId); //创建任务 { std::lock_guard<std::mutex> lock(g_mutex); g_task.push_back(ptask); std::cout << "produce a task Id is " << n_taskId << std::endl; } //唤醒线程 g_conv.notify_one(); n_taskId++; std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } } //消费者线程 void ConsumerFunc() { std::shared_ptr<CTask> ptask = nullptr; while (true) { std::unique_lock<std::mutex> lock(g_mutex); while (g_task.empty()) //即使被唤醒还要循环判断一次,防止虚假唤醒 { g_conv.wait(lock); } ptask = g_task.front(); //取出任务 g_task.pop_front(); if (ptask == nullptr) { continue; } ptask->dotask(); //执行任务 } } int main() { std::thread t1(ConsumerFunc); std::thread t2(ConsumerFunc); std::thread t3(ConsumerFunc); std::thread t4(ProdecerFunc); t1.join(); t2.join(); t3.join(); t4.join(); return 0; }
创建3个消费者线程,一个生产者线程,当存放任务的std::list为空时,消费者线程阻塞,当生产者线程生产一个任务放入std::list中时候,此时满足条件,条件变量就可以唤醒阻塞的线程去执行任务。
produce a task Id is 0 consumer a task Id is 0 produce a task Id is 1 consumer a task Id is 1 produce a task Id is 2 consumer a task Id is 2 produce a task Id is 3 consumer a task Id is 3 produce a task Id is 4 consumer a task Id is 4 produce a task Id is 5 consumer a task Id is 5 produce a task Id is 6 consumer a task Id is 6 produce a task Id is 7 consumer a task Id is 7 ......
条件变量的使用过程可以归纳如下:
- 拥有条件变量的线消费者程获取互斥锁;
- 消费者线程循环检查条件是否满足,不满足则阻塞等待,此时释放互斥锁;
- 当生产者线程产生任务后,调用notify_one()或者notify_all()唤醒阻塞的消费者线程;
- 当消费者线程被唤醒后再次获得互斥锁去执行任务;
4、thread_local
C++11中提供了thread_local
,thread_local
定义的变量在每个线程都保存一份副本,而且互不干扰,在线程退出的时候自动销毁。
#include <iostream> #include <thread> #include <chrono> thread_local int g_k = 0; void func1() { while (true) { ++g_k; } } void func2() { while (true) { std::cout << "func2 thread ID is : " << std::this_thread::get_id() << std::endl; std::cout << "func2 g_k = " << g_k << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } } int main() { std::thread t1(func1); std::thread t2(func2); t1.join(); t2.join(); return 0; }
在func1()
对g_k
循环加1操作,在func2()
每个1000毫秒输出一次g_k
的值:
func2 thread ID is : 15312 func2 g_k = 0 func2 thread ID is : 15312 func2 g_k = 0 func2 thread ID is : 15312 func2 g_k = 0 func2 thread ID is : 15312 func2 g_k = 0 func2 thread ID is : 15312 func2 g_k = 0 func2 thread ID is : 15312 func2 g_k = 0 func2 thread ID is : 15312 func2 g_k = 0 func2 thread ID is : 15312 func2 g_k = 0 func2 thread ID is : 15312 func2 g_k = 0 func2 thread ID is : 15312 func2 g_k = 0 ......
可以看出func2()
中的g_k
始终保持不变。
参考:
https://zhuanlan.zhihu.com/p/157171731