- c++线程同步方式
- linux线程是如何进行切换的
- 如何证明一个数据结构是线程安全的
- 并行编程中多进程和多线程,什么情况下多进程能解决的多线程无法解决
- 惊群现象
- 程序什么时候应该使用线程,什么时候单线程效率高
- 锁的种类
- condition variable使用方式
- shared_future
- promise, future, and async
- join和detach
- 死锁例子
- lock()函数,try_lock()
- unique_lock vs lockguard
- mutex
- conditional_variable
- future/promise
- 保存当前线程的状态:
- 选择新线程:
- 加载新线程的状态:
- 执行新线程:
无状态一定安全
单状态由一个线程安全的对象维护,也是安全的
多状态需要讨论
- 共享内存空间:线程间通信成本低,上下文切换速度快。
- 轻量级任务管理:资源消耗少
- 响应度提高:
- 利用多核处理器:
- 隔离性和安全性:
- 资源分配:
- 稳定性考虑:不会有死锁,一个崩溃不至于导致全盘崩溃
- 充分利用分布式系统:
当共享资源(如socket、文件等)可用时,多个等待该资源的线程或进程被同时唤醒竞争资源,但最终只有一个能够获得,造成其他被唤醒者无效唤醒,浪费了CPU资源。
使用线程:任务需要并行处理,或者io频繁,或者多核计算 选单线程:当任务顺序依赖、上下文切换开销大于并行收益时
- 互斥锁(Mutex):通过
pthread_mutex_t
类型和相关函数实现,保证同一时间只有一个线程访问临界区。 - 读写锁(Read-Write Lock):使用
pthread_rwlock_t
类型实现,允许多个读操作同时进行,但写操作是互斥的。 - 自旋锁(Spinlock):用于轻量级同步,当线程尝试获取锁时,它在一个循环中不断检查锁是否可用,适用于短时间等待。
- 条件变量(Condition Variable):通常与互斥锁配合使用,通过
pthread_cond_t
类型实现,允许线程在某种条件下挂起,直到另一个线程通知这个条件已经满足。
在 cv.wait(lck);
执行时,以下几件事情会发生:
- 当前线程会释放通过
unique_lock
获得的互斥锁mtx
并进入等待状态。 - 当
cv.notify_one()
或cv.notify_all()
被调用时,等待的线程之一(或全部,)会被唤醒。 - 一旦当前线程被唤醒(不管是因为通知还是因为虚假唤醒),它将再次尝试获取互斥锁。如果获取成功,
wait
函数返回,并持有锁。
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable
std::mutex mtx; // 全局互斥锁.
std::condition_variable cv; // 全局条件变量.
bool ready = false; // 全局标志位.
void do_print_id(int id)
{
std::unique_lock <std::mutex> lck(mtx);
while (!ready) // 如果标志位不为 true, 则等待...
cv.wait(lck); // 当前线程被阻塞, 当全局标志位变为 true 之后,
// 线程被唤醒, 继续往下执行打印线程编号id.
std::cout << "thread " << id << '\n';
}
void go()
{
std::unique_lock <std::mutex> lck(mtx);
ready = true; // 设置全局标志位为 true.
cv.notify_all(); // 唤醒所有线程.
}
int main()
{
std::thread threads[10];
// spawn 10 threads:
for (int i = 0; i < 10; ++i)
threads[i] = std::thread(do_print_id, i);
std::cout << "10 threads ready to race...\n";
go(); // go!
for (auto & th:threads)
th.join();
return 0;
}
执行结果如下:
10 threads ready to race...
thread 1
thread 0
thread 2
thread 3
thread 4
thread 5
thread 6
thread 7
thread 8
thread 9
复制代码
它的get()函数可以被多线程多次调用。
获取子线程的返回值
#include <future>
using namespace std;
int factorial(int N) {
int res = 1;
for (int i = N; i> 1; i--)
res *= i;
return res;
}
int main() {
int x;
// std::future<int> fu = std::async(factorial, 4);
// std::future<int> fu = std::async(std::launch::deferred | std::launch::async, factorial, 4);
// async 不一定就会新建新线程,新建线程必须加上std::launch::async 参数
std::future<int> fu = std::async(std::launch::async, factorial, 4);
// 如果加上std::launch::deferred 参数,则不会新建·线程,会在get的时候在当前线程运行factorial
// std::future<int> fu = std::async(std::launch::deferred, factorial, 4);
x = fu.get();
// fu.get(); error, crush ,不能二次使用get()
}
给子线程递送一个值
int factorial(std::future<int>& f) {
int res = 1;
int N = f.get();
return N;
}
int main() {
std::promise<int> p; // promise 不能被赋值和拷贝,只能用move
std::future<int> f = p.get_future();
std::future<int> fu = std::async(std::launch::async, factorial, std::ref(f));
// do something else
p.set_value(4);
int x = fu.get();
return 0;
}
使用join函数,原线程会等待新线程执行结束,再去销毁线程对象。
如果使用detach函数,新线程和老线程分离后,如果老线程执行完毕,由于新线程有自己的栈,新线程的局部变量不会被立即销毁。新线程如果使用了和老线程创建和共享的变量,则可能产生异常。这些资源用共享指针是个不错的办法。
线程A,B都需要用两个互斥量M,N。
A先占有M, 这时候B先占有了N。会发生AB两个线程都被卡住了。
锁住互斥量。使用try_lock()时,注意如果当前线程二次调用try_lock(same_mtx),会产生未定义行为。
lock_guard 对象并不负责管理 Mutex 对象的生命周期,lock_guard 对象只是简化了 Mutex 对象的上锁和解锁操作
unique_lock 可以随时加锁解锁,在condition_variable时候只能用他