C++11 标准库提供std::tread类用于多线程编程。
线程的创建和阻塞
线程的三种创建方式
- 函数名或函数指针创建线程
- 函数对象(仿函数)创建线程
- lambda表达式创建线程。
阻塞主线程:join()和detach()函数
两者区别:子线程对象调用join()
函数,主线程会被阻塞,直到子线程执行完成,主线程才能接着执行。子线程对象调用detach()
函数后,执行的子线程从线程对象中分离,并不会阻塞主线程的运行。
thread类的线程对象在被销毁前必须要调用join()
函数或者detach()
函数,此时线程对象没有关联的线程,会处于unjoinable
状态,可以正常调用析构函数。如果当前线程对象有关联的线程,即处于joinable
状态,此时调用对象的析构函数会出现运行异常terminate called without an active exception
。
PS:std::tread类的对象不能够进行拷贝,只能进行对象资源的转移。当使用std::move()将执行线程从当前对象转移到其他对象后,当前对象也会处于unjoinable
状态。
#include<thread>
#include<iostream>
using namespace std;
void thread_function(int n) {
for(int i = 0; i < 100; i++){
cout << "thread function " << n << " excuting" << endl;
}
}
void (*f)(int n); // 声明函数指针
class DisplayThread {
public:
void operator() (int n) {
for (int i = 0; i < 100; i++){
cout << "Display Thread " << n <<" Excecuting" << endl;
}
}
};
int main(){
/* 线程的创建者(父线程)必须管理创建的线程(子线程),应该等到子线程完成其任务或者让子线程从自己身上脱离。*/
// 线程创建
f = thread_function;
// thread threadObj(thread_function); // 用函数名创建线程。函数名同数组名一样都是个常量,表示函数体的首地址,并不是完全意义上的函数指针。
thread threadObj(f,1); //用函数指针创建线程
DisplayThread objectFunc;
// objectFunc(); //objectFunc()调用与thread_function()函数调用形式上完全一样
thread threadObj2(objectFunc,2); // 函数对象创建线程(函数对象是类的实例,调用操作符()被重载)
thread threadObj3([]{ //lambda表达式创建线程
for (int i = 0; i < 100; i++){
cout << "thread lambda expression excuting" << endl;
}
});
threadObj3.join(); // block线程,直到线程执行完毕,线程对象可以被销毁(threadObj3执行完后,才会接下去执行)
// threadObj3.detach(); // 将线程从线程对象中分离,线程对象可以被销毁(被分离的线程仍与其它的线程并行执行)
for (int i = 0; i < 20; i++){
cout << "Display from MainThread" << endl;
}
thread threadObj4; // 默认初始化对象,还未传入回调函数
cout << "before starting, unjoinable: " << threadObj4.joinable() << '\n';//0
threadObj4 = thread( (DisplayThread()) , 4); // 函数对象创建线程(通过DisplayThread()创建DisplayThread类的临时对象)
cout << "after starting, joinable: " << threadObj4.joinable() << '\n'; //1
thread threadObj5 = std::move(threadObj4); // 线程对象不能拷贝,只能转移。move()将threadObj4对象的资源转移到threadObj5
cout << threadObj.get_id() << endl; // 2
cout << threadObj2.get_id() << endl; // 3
cout << threadObj3.get_id() << endl; // 4
cout << "after move, joinable: " << threadObj4.joinable() << '\n'; // 0
// cout << threadObj4.get_id() << endl; // threadObj4对象的线程已经被转移,处于unjoinable状态
cout << threadObj5.get_id() << endl; // 5
cout << this_thread::get_id() << endl; //返回当前线程id, 1
threadObj.join();
threadObj2.join();
// threadObj4.join(); // unjoinable的线程对象不能调用join()或者detach()
threadObj5.join();
cout << "after join(), unjoinable: " << threadObj4.joinable() << '\n';//0
cout << "Exit of Main function" << endl;
return 0;
}
小结:不要在没有关联执行线程(unjoinable状态)的thread对象上调用join()或者detach()函数;不要忘记在关联了执行线程(joinable状态)的thread对象上调用join()或者detach()。
PS:如果主线程运行完了,那些被detach分离的线程也会随着主线程的销毁而被挂起,停止运行。
线程的互斥和同步
互斥:互斥量
同步:互斥量+条件变量
条件变量(condition_variable)的使用过程:
- 拥有条件变量的线程获取互斥量。
unique_lock<mutex> locker(mtx);
- 循环检查某个条件,如果条件不满足,则阻塞当前线程直到条件满足;如果条件满足则向下执。
while(!firstOK){ cond.wait(locker); }
// 当条件变量的对象调用wait()函数时,它使用互斥量锁住当前线程。
// 当前线程一直被阻塞,直到另外一个线程在同一个条件变量的对象上调用notify_one()或notify_all()函数来唤醒当前线程。
// 用while是因为如果当前条件不满足,线程就算被唤醒了,依然需要被继续阻塞等待条件满足。
- 某个线程满足条件执行完后,调用
notify_one()
或notify_all()
唤醒一个或者所有等待的线程。
cond.notify_all();
同步实例:leetcode-按序打印
#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<functional>
using namespace std;
class Foo {
public:
int count = 0;
Foo():firstOK(false), secondOK(false) {
}
void first(function<void()> printFirst) {
unique_lock<mutex> locker(mtx); // std::unique_lock<T>为锁管理模板类,std::unique_lock对象独占mutex对象的所有权,管理mutex对象的上锁和解锁操作
// printFirst() outputs "first". Do not change or remove this line.
printFirst();
firstOK = true;
count++;
cond.notify_all();
}
void second(function<void()> printSecond) {
unique_lock<mutex> locker(mtx);
while(!firstOK){
cond.wait(locker);
}
// printSecond() outputs "second". Do not change or remove this line.
printSecond();
secondOK = true;
count++;
cond.notify_all();
}
void third(function<void()> printThird) {
unique_lock<mutex> locker(mtx);
while(!secondOK){
cond.wait(locker);
}
// printThird() outputs "third". Do not change or remove this line.
printThird();
count++;
cond.notify_all();
}
private:
mutex mtx;
condition_variable cond;
bool firstOK;
bool secondOK;
};
void printFirst(){
cout << "first" << endl;
}
void printSecond(){
cout << "second" << endl;
}
void printThird(){
cout << "third" << endl;
}
int main(){
// Foo* f = new Foo();
// thread t1(&Foo::first, f, printFirst);
// thread t3(&Foo::third, f, printThird);
// thread t2(&Foo::second, f, printSecond);
Foo f;
// Foo* ff = new Foo();
// std::thread类成员函数作为线程函数,第一个参数是函数指针,第二个参数是对象指针(在类成员函数中创建线程,即this指针)
// 三个线程共用一个Foo的对象,共享互斥量、条件变量等
thread t1(&Foo::first, &f, printFirst);
thread t3(&Foo::third, &f, printThird);
// thread t3(&Foo::third, ff, printThird); // 不是共用一个对象会出现死锁,因为两个对象之间的互斥量,条件变量不共享
thread t2(&Foo::second, &f, printSecond);
t1.join();
t2.join();
t3.join();
cout << f.count << endl;
// cout << ff->count << endl;
}
PS:std::thread类成员函数作为线程函数,第一个参数是函数指针,第二个参数是对象指针(在类成员函数中创建线程,即this指针) 参考博客