C++11 增加了对多线程的支持,是多线程编程变得简单、易用。
一、线程的创建
#include <string>
#include <thread>
//用于时间延时 获取时间
#include <chrono>
#include <iostream>
using namespace std;
void test_create(string str){
for(int i=0; i<10; i++){
//让本线程休眠100毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(100));
cout << "sleep for : 100ms, string:" << str << endl;
}
}
void createThread(){
//第一个参数是t1线程要执行的函数,第二个参数是test_create函数的参数
thread t1(test_create, "hello");
//阻塞当前线程,直到t1执行完成,
//如果不用join也可以使用detach,不过用了detach之后,该线程就变成了孤儿线程
t1.join();
cout << " t1 is end" << endl;
}
二、互斥量的使用
当多个线程共同访问一个临界区的时候,如果没有任何处理会造成数据安全问题。我们可以使用互斥量来解决此问题。
头文件mutex
#include <mutex>
int num = 0;
mutex t_lock;
void test_mutex_1(){
for(int i=0; i<100; i++){
//此方式需要自行加锁解锁
t_lock.lock();
num++;
//临界区访问结束后,需要解锁
t_lock.unlock();
}
}
void test_mutex_2(){
for(int i=0; i<100; i++){
t_lock.lock();
num++;
t_lock.unlock();
}
}
void test_mutex(){
cout << "before num:" << num << endl;
//多线程共同访问一块临界区,需要用到互斥量
thread t1(test_mutex_1);
thread t2(test_mutex_2);
t1.join();
t2.join();
cout << "after num:" << num << endl;
}
C++11还提供了lock_guard和unique_guard来处理锁的问题。
lock_guard的使用:
lock_guard使用了RAII机制可以确保安全释放mutex。在构造lock_guard对象的时候,需要传入mutex对象,此时mutex会被当前线程锁住,直到lock_guard对象被析构的时候,才会释放mutex。不需要我们自己做加锁和解锁的操作。
lock_guard不负责mutex生命周期的管理,它只是简化了mutex的加锁、解锁的操作。lock_guard生命周期内,mutex处于加锁状态,lock_guard生命周期结束后,它所管理的锁会被释放。
void test_lock_guard_1(){
lock_guard<mutex> lg(t_lock);
for(int i=0; i<100; i++){
num++;
}
}
void test_lock_guard_2(){
lock_guard<mutex> lg(t_lock);
for(int i=0; i<100; i++){
num++;
}
}
void test_lock_guard(){
cout << "before num:" << num << endl;
thread t1(test_lock_guard_1);
thread t2(test_lock_guard_2);
t1.join();
t2.join();
cout << "after num:" << num << endl;
}
unique_lock的使用:
除了提供与lock_guard相同的功能之外,还提供了更多的函数,相较于lock_guard来说,更加的灵活一些。通过使用unique_lock的函数可以灵活的控制锁的范围,减小锁的粒度,并且还可以控制锁的时间等。
unique_lock内部持有mutex的状态:lock和unlock。所以unique_lock比lcok_guard更加的占用空间,并且速度会更慢一些,因为它要维护mutex的状态。
lock | locks the associated mutex (public member function) |
try_lock | tries to lock the associated mutex, returns if the mutex is not available (public member function) |
try_lock_for | attempts to lock the associated TimedLockable mutex, returns if the mutex has been unavailable for the specified time duration (public member function) |
try_lock_until | tries to lock the associated TimedLockable mutex, returns if the mutex has been unavailable until specified time point has been reached (public member function) |
unlock | unlocks the associated mutex |
unique_guard() | 默认构造函数,此时不用任何的mutex |
unique_lock(mutex_type &m) | 获取mutex,并调用mutex.lock()对其加锁 |
unique_lock(mutex_type &m, try_to_lock_t_tag) | tag=try_lock表示调用mutex.try_lock()尝试加锁 |
unique_lock(mutex_type &m, defer_lock_t_tag) | tag=defer_lock,表示不加锁,只是管理mutex,此时mutex是不加锁状态 |
unique_lock(mutex_type &m, adopt_lock_t_tag) | tag=adopt_lock,表示在此之前mutex已经加锁,此时unique_guard管理mutex |
unique_lock(mutex_type &m, const chrono::duration<Rep, Period>& rel_time) | 在一段时间内尝试对mutex加锁,mutex.try_lock_for(rel_time) |
unique_lock(mutex_type &m, const chrono::time_point<Clock, Duration>& abs_time) | 直到abs_time尝试加锁 |
unique_lock(unique_lock&& x) | 获得x管理的mutex,此后mutex不再和x相关联,此后x相当于一个默认构造的unique_lock。 |
条件变量
可以使用条件变量来实现多个线程之间的同步操作,当条件不满足的时候,一直被阻塞,当满足条件的时候,线程才会被唤醒。
条件变量是利用线程间共享的全局变量进行同步的一种机制主要包含两个动作:
- 一个线程因等待“条件变量的条件成立”而挂起
- 另外一个线程是“条件成立”,给出信号,从而唤醒被挂起的线程。
为了防止竞争,条件变量总是和一个互斥锁一起使用,通常情况下锁是std::mutex,并且管理这个锁只能是unique_lock
mutex con_mutex;
//线程共享的全局变量
condition_variable con_cv;
void show(int id)
{
std::unique_lock<std::mutex> lck(con_mutex);
con_cv.wait(lck);
std::cout << "id:" << id << std::endl;
}
void notify()
{
std::unique_lock<std::mutex> lck(con_mutex);
con_cv.notify_all(); //唤醒所有在此条件变量上的等待线程
}
void test_condition(){
std::thread t[8];
for (int i = 0; i < 8; i++)
{
t[i] = std::thread(show, i);
}
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << "all thread lock......" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2));
notify();
for (auto & th : t) th.join();
}
三、原子类
类似于Java并发包中的Atomic类。C++11引入了原子操作的概念,原子操作更接近内核,编译器保证这些操作都是原子性的,即任何时候都只有一个线程访问这些资源,避免了锁的使用,提高了效率。
atomic_int atomic_num(0);
void atomic_thread(){
for(int i=0; i<100; i++){
atomic_num++;
}
}
void test_atomic(){
cout << "befor, num:" << atomic_num << endl;
thread t1(atomic_thread);
thread t2(atomic_thread);
t1.join();
t2.join();
cout << "befor, num:" << atomic_num << endl;
}