标准线程库,c++11引入,包含原子操作库、互斥锁、条件变量。。。
一、线程库<thread>
创建线程的四种方法:
1. 通过全局函数创建线程
线程类的构造函数是变参构造函数,第一个参数是线程函数,后面的参数为线程函数的参数(参数通过值传递方式,若要引用传递须加std::ref())。
thread t1 (counter, 1, 6); //void counter(int, int);
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));
比较第一种和第三种构造方式,如果函数对象的构造函数不需要任何参数。 thread t3(Counter());是不行的,因为编译器会认为你在声明一个函数,函数名为t3,此时只能用第一种构造方式。
3. 通过lambda表达式创建线程
thread t1 ([](int, int){/*函数体*/}, 1, 6);
4. 通过成员函数创建线程
// class Counter c();
thread t{&Counter::process, &c};
一般常见的是一个类自己创建一个后台处理线程:thread t{&Counter::process, this};
线程本地存储 thread_local
thread_local int n;
n作为线程参数传递给线程,那么每个线程有一个n的副本,在线程整个生命周期中存在,且只初始化一次,如同static局部变量。
二、原子操作库<atomic>
多线程编程经常需要操作共享的内存,在读/写过程中会导致竞争条件。
例如:
int counter = 0;
............
++counter; //因为++counter不时原子操作,多个线程中出现此操作时不是线程安全的。
应该用:
atomic<int> counter(0); //等效于 atomic_int counter(0);
............
++counter; //此时多个线程执行++counter是一个原子操作,是线程安全的。
例: 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 ) //线程参数总是值传递,若要传递引用,须加std::ref()。(头文件<functional>中) threads.push_back( std::thread{ func, std::ref(counter)} ); for( auto& t : threads ) t.join(); //调用join,如果线程未结束,则main函数阻塞于此。 std::count<<"Result="<<counter<<std::endl; return 0; } /*join的调用会导致调用线程阻塞于此,若不希望调用线程阻塞,但又想知道被调线程是否结束,应当用其它方式,例如消息...*/
三、互斥 <mutex>
编写多线程必须分外留意操作顺序,如果无法避免线程共享数据,则必须提供同步机制,保证一次只有一个线程能更改数据。使用互斥解决竞争条件,可能导致死锁。
1. 互斥体类
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区别同上。
2. 锁类
锁类是一个包装器,析构函数会自动释放关联的互斥体。
1) 简单锁 std::lock_guard
其构造函数会要求获得互斥体,并阻塞直到获得锁。
2) 复杂锁 std::unique_lock
explict unique_lock( mutex_type& m); //阻塞直到获得锁。 unique_lock(mutex_type& m, defer_lock_t) noexcept; //保存一个互斥体引用,不会立即尝试获得锁。锁可以在以后获得。 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()运算符。
3. 获得多个互斥体对象上的锁
1) 泛型lock可变参数模板函数
template <class L1, class L2, class...L3>
void lock(L1&, L2&, L3&...);
按顺序锁定,如果一个互斥体抛出异常,会对已获得的锁unlock。
2) 泛型try_lock
template <class L1, class L2, class...L3>
int try_lock(L1&, L2&, L3&...);
通过顺序调用互斥体对象的try_lock,成功返回-1,失败返回从0开始的位置索引,并对已获得的锁unlock。
参数顺序每次应保持一致, 否则易死锁。
4. std::call_once std::once_flag
保证call_once调度的函数只被执行一次。
5. 实例:
// 1. 简单锁 mutex mMutex; lock_guard<mutex> mLock(mMutex); // 2. 定时锁 timed_mutex mTimeMutex; unique_lock<timed_mutex> mLock(mTimedMutex, chrono::milliseconds(200)); // 3. 泛型 mutex mut1; mutex mut2; unique_lock<mutex> lock1(mut1, defer_lock_t()); unique_lock<mutex> lock2(mut2, defer_lock_t()); lock(lock1, lock2); // 4. 双重检查锁定算法 (代替call_once的用法) class MyClass { public: void init() { p = new int(0); cout<<"Init"<<endl;} private: int *p; } MyClass var; bool initialized = false; mutex mut; void func() { if( ! initialized) //一次检查 { unique_lock<mutex> lock1(mut); if( ! initialized) //两次检查 { var.init(); initialized = true; } } cout<<"OK"<<endl; } //两次检查initialized。获得锁之前和获得锁之后,确保init只调用一次。
四、条件变量 <condition_variable>
1. std::condition_variable 只能等待unique_lock<mutex>的条件变量
notify_one(); //唤醒等待这个条件变量的线程之一 notify_all(); //唤醒所有等待这个条件变量的线程 // 1)前提是已经获得lk的锁 // 2)调用wait会unlock lk,然后等待 // 3)当被唤醒后 lock lk wait( unique_lock<mutex>& lk); wait_for(unique_lock<mutex>& lk, const chrono::duration<Rep,Period>& rel_time); wait_until(unique_lock<mutex>&lk, const chrono::time_point<Clock,Duration>& abs_time);
2. std::condition_variable_any 支持任何类型的Lock类
3.
//例:向队列中加入数据,当队列不为空时,后台线程被唤醒处理数据 std::queue<std::string> mQueue; std::mutex mMutex; std::condition_variable mCondVar; //向队列加入数据的线程 unique_lock<mutex> lock(mMutex); mQueue.push( data); mCondVar.notify_all(); //后台处理数据的线程 unique_lock<mutex> lock(mMutex); while(true) { //1.先释放lock 2.然后等待被唤醒 3.被唤醒后等待获取lock mCondVar.wait(lock); // process... }