一、C++11多线程thread
1.1 线程thread
std::thread 在 #include 头文件中声明,因此使用 std::thread 时需要包含 #include 头文件。
1.1.1 构造函数
1、默认构造函数
//创建一个空的 thread 执行对象。 thread() _NOEXCEPT { // construct with no thread _Thr_set_null(_Thr); }
2、初始化构造函数
//创建std::thread执行对象,该thread对象可被joinable,新产生的线程会调用threadFun函数,该函数的参数由 args 给出 template<class Fn, class... Args> explicit thread(Fn&& fn, Args&&... args);
3、拷贝构造函数
// 拷贝构造函数(被禁用),意味着 thread 不可被拷贝构造。 thread(const thread&) = delete; thread t1; thread t2 =t1; // 错误
4、Move构造函数
//move 构造函数,调用成功之后, x 不代表任何 thread 执行对象。 //注意:可被 joinable 的 thread 对象必须在他们销毁之前被主线程 join 或者将其设置为detached。 thread(thread&& x)noexcept thread t1; thread t2 =move(t1); // 可以
5、范例
#include<thread> #include<iostream> using namespace std; void threadFun(int &a) // 引用传递 { cout << "this is thread fun !" <<endl; cout <<" a = "<<(a+=10)<<endl; } int main() { int x = 10; thread t1(threadFun, std::ref(x)); // std::ref(x) 创建了对变量 x 的引用。 thread t2(std::move(t1)); // t1 线程失去所有权 thread t3; t3 = std::move(t2); // t2 线程失去所有权 t3.join(); cout<<"Main End "<<"x = "<<x<<endl; return 0; }
this is thread fun ! a = 20 Main End x = 20
1.1.2 主要成员函数
- get_id()
获取线程ID,返回类型std::thread::id对象。
http://www.cplusplus.com/reference/thread/thread/get_id/
- - joinable()
判断线程是否可以加入等待
http://www.cplusplus.com/reference/thread/thread/joinable/
- join()
等该线程执行完成后才返回。
http://www.cplusplus.com/reference/thread/thread/join/
- detach()
detach调用之后,目标线程就成为了守护线程,驻留后台运行,与之关联的std::thread对象失去对目标线程的关联,无法再通过std::thread对象取得该线程的控制权。当线程主函数执行完之后,线程就结束了,运行时库负责清理与该线程相关的资源。
调用 detach 函数之后:
1)*this 不再代表任何的线程执行实例。
2)joinable() == false
3)get_id() == std::thread::id()
http://www.cplusplus.com/reference/thread/thread/detach/
1.1.3 范例示范
#include <iostream> #include <thread> // 头文件 using namespace std; // 1 传入0个值 void func1() { cout << "func1 into" << endl; } // 2 传入2个值 void func2(int a, int b) { cout << "func2 a + b = " << a+b << endl; } void func2_1(int a, int b) { cout << "func2_1 a + b = " << a+b << endl; } int func2_1(string a, string b) { cout << "func2_1 a + b = " << a << b<< endl; return 0; } // 3 传入引用 void func3(int &c) // 引用传递 { cout << "func3 c = " << &c << endl; c += 10; } // class A { public: // 4. 传入类函数 void func4(int a) { // std::this_thread::sleep_for(std::chrono::seconds(1)); cout << "thread:" << name_<< ", fun4 a = " << a << endl; } void func4(string str) { // std::this_thread::sleep_for(std::chrono::seconds(1)); cout << "thread:" << name_<< ", fun4 str = " << str << endl; } void setName(string name) { name_ = name; } void displayName() { cout << "this:" << this << ", name:" << name_ << endl; } void play() { std::cout<<"play call!"<<std::endl; } private: string name_; }; //5. detach void func5() { cout << "func5 into sleep " << endl; std::this_thread::sleep_for(std::chrono::seconds(1)); cout << "func5 leave " << endl; } // 6. move void func6() { cout << "this is func6 !" <<endl; }
在mian函数里分别调用下面的各种情况
1)传入0个值
// 1. 传入0个值 cout << "\n\n main1--------------------------\n"; std::thread t1(func1); // 只传递函数 t1.join(); // 阻塞等待线程函数执行结束
main1-------------------------- func1 into
2)传入2个值
第一种:
int a =10; int b =20; std::thread t2(func2, a, b); // 加上参数传递,可以任意参数 t2.join();
func2 a + b = 30
第二种:
int a =10; int b =20; std::thread t2((void(*)(int, int)) func2_1, a, b); // 加上参数传递,可以任意参数 t2.join();
func2_1 a + b = 30
第三种
std::thread t2_2((int(*)(string, string)) func2_1, "zxm", " and mark"); // 加上参数传递,可以任意参数 t2_2.join();
func2_1 a + b = zxm and mark
3)传入引用
int c =10; std::thread t3(func3, std::ref(c)); // std::ref 加上参数传递,可以任意参数 t3.join(); cout << "main3 c = " << &c << ", "<<c << endl;
func3 c = 0x61fd34 main3 c = 0x61fd34, 20
4)传入类函数
重载1
A * a4_ptr2 = new A(); a4_ptr2->setName("king"); std::thread t41((void(A::*)(int))&A::func4, a4_ptr2, 100); // 重载void func4(int a),(A::*) 表示这是一个成员函数指针 t41.join(); delete a4_ptr2;
thread:king, fun4 a = 100
重载2
A * a4_ptr3 = new A(); a4_ptr3->setName("Darren"); std::thread t43((void(A::*)(string))&A::func4, a4_ptr3, "and zxm"); // 重载 int func4(string str) t43.join(); delete a4_ptr3;
thread:Darren, fun4 str = and zxm
5)detach
std::thread t5(&func5); // 只传递函数 t5.detach(); // 脱离 cout << "pid: " << t5.get_id() << endl; // t5此时不能管理线程了 cout << "joinable: " << t5.joinable() << endl; // false // t5.join(); 这里不能用join了,会直接崩掉 cout << "\n main5 end\n";
func5 into sleep pid: thread::id of a non-executing thread joinable: 0 main5 end
6)move
int x = 10; thread t6_1(func6); thread t6_2(std::move(t6_1)); // t6_1 线程失去所有权 // t6_1.join(); // 抛出异常 after throwing an instance of 'std::system_error' t6_2.join();
this is func6 !
1.2 互斥量
mutex又称互斥量,C++ 11中与 mutex相关的类(包括锁类型)和函数都声明在 头文件中,所以如果你需要使用 std::mutex,就必须包含 头文件。
C++11提供如下4种语义的互斥量(mutex)
1)std::mutex,独占的互斥量,不能递归使用。
2)std::time_mutex,带超时的独占互斥量,不能递归使用。
3)std::recursive_mutex,递归互斥量,不带超时功能。
4)std::recursive_timed_mutex,带超时的递归互斥量。
1.2.1 独占互斥量std::mutex
std::mutex 是C++11 中最基本的互斥量,std::mutex 对象提供了独占所有权的特性— —即不支持递归地对std::mutex对象上锁,而 std::recursive_lock 则可以递归地对互斥量对象上锁。
(可重入互斥锁(Reentrant Mutex),也称为递归互斥锁,是一种特殊类型的互斥锁。它允许同一线程多次获得该锁而不会产生死锁。)
std::mutex 的成员函数
1)构造函数,std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于unlocked状态的。
2)lock(),调用线程将锁住该互斥量。线程调用该函数会发生下面 3 种情况:
- 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。
- 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。
- 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。
3)unlock(), 解锁,释放对互斥量的所有权。
4)try_lock(),尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞。线程调用该函数也会出现下面 3 种情况
- 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock 释放互斥量。
- 如果当前互斥量被其他线程锁住,则当前调用线程返回false,而并不会被阻塞掉。
- 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。
1.2.2 范例
#include <iostream> // std::cout #include <thread> // std::thread #include <mutex> // std::mutex volatile int counter(0); // non-atomic counter std::mutex mtx; // locks access to counter void increases_10k() { for (int i=0; i<10000; ++i) { // 1. 使用try_lock的情况 if (mtx.try_lock()) { // only increase if currently not locked: ++counter; mtx.unlock(); } // 2. 使用lock的情况 // { // mtx.lock(); // ++counter; // mtx.unlock(); // } } } int main() { std::thread threads[10]; for (int i=0; i<10; ++i) threads[i] = std::thread(increases_10k); for (auto& th : threads) th.join(); std::cout << " successful increases of the counter " << counter << std::endl; return 0; }
使用try_lock的情况,不会阻塞
successful increases of the counter 15533
使用lock的情况,会阻塞
successful increases of the counter 100000
1.2.3 lock_guard和unique_lock的使用和区别
1、unique_lock,lock_guard的使用
自动释放锁
#include <iostream> // std::cout #include <thread> // std::thread #include <mutex> // std::mutex, std::lock_guard #include <stdexcept> // std::logic_error std::mutex mtx; void print_even (int x) { if (x%2==0) std::cout << x << " is even\n"; else throw (std::logic_error("not even")); } void print_thread_id (int id) { try { // 这里的lock_guard换成unique_lock是一样的。 // using a local lock_guard to lock mtx guarantees unlocking on destruction / exception: // std::lock_guard<std::mutex> lck (mtx); std::unique_lock<std::mutex> lck (mtx); print_even(id); } catch (std::logic_error&) { std::cout << "[exception caught]\n"; } } int main () { std::thread threads[10]; // spawn 10 threads: for (int i=0; i<10; ++i) threads[i] = std::thread(print_thread_id,i+1); for (auto& th : threads) th.join(); return 0; }
[exception caught] 2 is even [exception caught] 4 is even [exception caught] 6 is even [exception caught] 8 is even [exception caught] 10 is even
主要看std::unique_lock lck (mtx);,以往我们都是
mtx.lock(); print_even(id); mtx.unlock();
需要我们手动释放锁,但在if else 这种多return分支中,很容易忘记释放。
而std::lock_guard lck (mtx);的lck是在栈上的,当退出作用域后会调用析构函数,自动释放锁。
std::unique_lock lck (mtx);也类似,能自动释放锁。
2、unique_lock,lock_guard的区别
1)unique_lock与lock_guard都能实现自动加锁和解锁,但是前者更加灵活,能实现更多的功能。
2)unique_lock可以进行临时解锁和再上锁,如在构造对象之后使用lck.unlock()就可以进行解锁,lck.lock()进行上锁,而不必等到析构时自动解锁。
#include <iostream> #include <deque> #include <thread> #include <mutex> #include <condition_variable> #include <unistd.h> std::deque<int> q; std::mutex mu; std::condition_variable cond; int count = 0; void fun1() { while (true) { std::unique_lock<std::mutex> locker(mu); std::cout << "fun1 lock\n"; q.push_front(count++); locker.unlock(); // lock_guard是没有手动释放锁的 unlock cond.notify_one(); sleep(1); } } void fun2() { while (true) { std::unique_lock<std::mutex> locker(mu); std::cout << "fun2 lock\n"; std::cout << "fun2 wait into\n"; cond.wait(locker, [](){return !q.empty();}); std::cout << "fun2 wait leave\n"; auto data = q.back(); q.pop_back(); locker.unlock(); std::cout << "thread2 get value form thread1: " << data << std::endl; } } int main() { std::thread t1(fun1); std::thread t2(fun2); t1.join(); t2.join(); return 0; }
上面代码中fun1插入数据(生产者),fun2pop出数据(消费者)。每次加锁之后,需要临时解锁,否则会造成死锁,即
std::unique_lock<std::mutex> locker(mu); …… locker.unlock(); // lock_guard是没有手动释放锁的 unlock
但是如果换成lock_guard,那就不行了,因此lock_guard是没有手动释放锁的 unlock接口。
当然,也可以加大括号的方式,出了大括号就相当于出了作用域。这时候两个都可以,因为不用unlock
while (true) { { std::lock_guard<std::mutex> locker(mu); std::cout << "fun1 lock\n"; q.push_front(count++); } cond.notify_one(); sleep(1); }
1.3 条件变量
互斥量是多线程间同时访问某一共享变量时,保证变量可被安全访问的手段。但单靠互斥量无法实现线程的同步。线程同步是指线程间需要按照预定的先后次序顺序进行的行为。C++11对这种行为也提供了条件变量。条件变量位于头文件condition_variable下。
条件变量使用过程:
1)拥有条件变量的线程获取互斥量;
2)循环检查某个条件,如果条件不满足则阻塞直到条件满足;如果条件满足则向下执行;
3)某个线程满足条件执行完之后调用notify_one或notify_all唤醒一个或者所有等待线程。
1.3.1 wait函数
void wait (unique_lock<mutex>& lck); template <class Predicate> void wait (unique_lock<mutex>& lck, Predicate pred);
包含两种重载,第一种只包含unique_lock对象,另外一个Predicate 对象(等待条件),这里必须使用unique_lock,因为wait函数的工作原理:
1)当前线程调用wait()后将被阻塞并且函数会解锁互斥量,直到另外某个线程调用notify_one或者notify_all唤醒当前线程;一旦当前线程获得通知(notify),wait()函数也是自动调用lock(),同理不能使用lock_guard对象。
2)如果wait()没有第二个参数,第一次调用默认条件不成立,直接解锁互斥量并阻塞到本行,直到某一个线程调用notify_one或notify_all为止,被唤醒后,wait()重新尝试获取互斥量,如果得不到,线程会卡在这里,直到获取到互斥量,然后无条件地继续进行后面的操作。
3)如果wait()包含第二个参数,如果第二个参数不满足,那么wait()将解锁互斥量并堵塞到本行,直到某一个线程调用notify_one或notify_all为止,被唤醒后,wait()重新尝试获取互斥量,如果得不到,线程会卡在这里,直到获取到互斥量,然后继续判断第二个参数,如果表达式为false,wait()对互斥量解锁,然后休眠,如果为true,则进行后面的操作。
1.3.2 wait_for函数
template <class Rep, class Period> cv_status wait_for (unique_lock<mutex>& lck, const chrono::duration<Rep,Period>& rel_time); template <class Rep, class Period, class Predicate> bool wait_for (unique_lock<mutex>& lck, const chrono::duration<Rep,Period>& rel_time, Predicate pred);
和wait不同的是,wait_for可以执行一个时间段,在线程收到唤醒通知或者时间超时之前,该线程都会处于阻塞状态,如果收到唤醒通知或者时间超时,wait_for返回,剩下操作和wait类似。
1.3.3 wait_until函数
template <class Clock, class Duration> cv_status wait_until (unique_lock<mutex>& lck, const chrono::time_point<Clock,Duration>& abs_time); template <class Clock, class Duration, class Predicate> bool wait_until (unique_lock<mutex>& lck, const chrono::time_point<Clock,Duration>& abs_time, Predicate pred);
与wait_for类似,只是wait_until可以指定一个时间点,在当前线程收到通知或者指定的时间点超时之前,该线程都会处于阻塞状态。如果超时或者收到唤醒通知,wait_until返回,剩下操作和wait类似
1.3.4 notify_one函数
void notify_one() noexcept;
解锁正在等待当前条件的线程中的一个,如果没有线程在等待,则函数不执行任何操作,如果正在等待的线程多余一个,则唤醒的线程是不确定的(随机唤醒一个)。
1.3.5 notify_all函数
void notify_all() noexcept;
解锁正在等待当前条件的所有线程,如果没有正在等待的线程,则函数不执行任何操作