thread
语言层面实现了线程,则支持了跨平台,由底层决定具体调用哪种函数来创建线程(linux下使用pthread_create()) STL中的容器都不是线程安全的,所以一般需要自己封装这些容器,然后再在多个线程间共享
std::thread t1(函数对象/指针,参数);
t1.join();
//主线程等待t1子线程结束,主线程继续向下执行t1.detach();
//t1子线程分离,主线程结束,整个进程结束,所有子线程被杀死
同步与互斥
mutex
- 临界区:多线程间读写共享资源的代码片段(如全局变量、内存、文件等)
- 对临界区上锁
- 存在共享变量判断时,最好使用锁加双重判断,能增大并发
std::mutex mtx;
mtx.lock();
mtx.unlock();
//不释放会可能导致死锁
lock_guard
如果在临界区退出了,那锁就不会释放,导致出现死锁 采用与智能指针类似思路,使用栈对象来管理锁,出作用域后栈对象析构的同时调用释放锁的函数 不允许复制,也不存在移动语句,所以不能进行函数之间的传递
std::mutex mtx;
lock_guard<std::mutex> lock(mtx);
//构造函数中上锁,析构函数中释放锁
unique_lock
也不允许复制,但是存在移动语义,允许将锁转移 也是在析构时会调用unlock()
std::mutex mtx;
unique_lock<std::mutex> lck(mtx);
lck.lock();
lck.unlock();
condition_variavble
- 进程间同步机制,两个进程都需要访问临界变量,但是两者又有牵制,比如生产和消费,由于消费者进入临界区已经上了锁,就必须在内部释放锁后等待生产者进入临界区生成,生成完后再告知消费者;
- 传入的是unique_lock(),而不是简单的mutex;
- 使用wait后会释放锁,并阻塞在条件变量上,等待其他线程通知,通知之后,会立即加锁然后向下执行,但是由于生产者生成了一个,可能唤醒多个消费者,所以需要唤醒后依然判断是否为空,为空代表被其他消费者消费,则本消费者又该进入阻塞态;
std::mutex mtx;
std::condition_variable cv;
std::unique_lock<std::mutex> lck(mtx);
cv.wait(lck);
//这里是引用传递cv.notify_one();
cv.notify_all();
atomic
互斥锁开销比较大的,如果临界区操作简单,则不建议使用互斥锁 atomic就是一个类模板,重载了部分操作符,如++等,保证了原子性,且不允许拷贝和移动 线程访问全局变量存在cpu缓存,如果没有感知该变量变化(如声明了const或其他线程修改),则直接从该缓存中读取,导致其他线程修改后对本线程不可见而出现问题,使用关键字
volatile
来确保每次读取都是从内存
- `std::atomic value(10)