互斥量
互斥量解决多线程数据共享问题
当多线程去共享同一个数据的时候,会造成争夺
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include <iostream> #include <thread>
int a = 0; void func() { for (int i = 0; i < 10000; i++) { a += 1; } }
int main() { std::thread t1(func); std::thread t2(func); t1.join(); t2.join(); std::cout << a << std::endl; system("pause"); return 0; }
|
运行上述代码发现,a的值没有如我们预期所认为是20000,而是随机的数字。这是因为线程在读取同一个数据的时候发生了争夺。
解决办法:当一个线程拿了数据,其他线程禁止拿,也就是互斥锁:线程访问共享资源前,先加锁(lock),用完后解锁(unlock)。
1 2 3 4 5 6 7 8 9 10
| #include <mutex> std::mutex mtx;
void func() { for (int i = 0; i < 10000; i++) { mtx.lock(); a += 1; mtx.unlock(); } }
|
多线程安全:如果多线程程序每一次的运行结果和单线程运行的结果是一样的,那么你的线程就是安全的。
互斥量死锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #include <iostream> #include <thread> #include <mutex>
std::mutex m1,m2; void func_1() { for (int i = 0; i < 50; i++) { m1.lock(); m2.lock(); m1.unlock(); m2.unlock(); } }
void func_2() { for (int i = 0; i < 50; i++) { m2.lock(); m1.lock(); m1.unlock(); m2.unlock(); } }
int main() { std::thread t1(func_1); std::thread t2(func_2); t1.join(); t2.join(); std::cout << "over" << std::endl; system("pause"); return 0; }
|
解决方法,当某个线程获取到了m1,那就让他获取m2,按照这样的规则,所有的线程都得先有m1才有m2,那其他线程拿不到m1自然也拿不到m2,所以调换func_2的m1、m2顺序即可。
lock_guard与unique_lock
std::lock_guard是C++标准库中的一种互斥量封装类,用于保护共享数据,防止多个线程同时访问同一资源而导致的数据竞争问题。
- 当构造函数被调用时,该互斥量会被
自动锁定
- 当析构函数被调用时,该互斥量会被
自动解锁
- std::lock_guard对象不能复制或移动,因此它
只能在局部作用域中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <iostream> #include <mutex> #include <thread>
int shared_data = 0;
std::mutex mtx; void func() { for (int i = 0; i < 10000; i++) { std::lock_guard<std::mutex>lg(mtx); shared_data++; } }
int main() { std::thread t1(func); std::thread t2(func); t1.join(); t2.join();
std::cout << shared_data << std::endl;
return 0;
}
|
lock_guardlg(mtx)的作用就相当于mtx.lock() 且 mtx.unlock().
有五个用法:
标准用法
1 2 3 4 5 6
| std::mutex mtx; void func() { std::unique_lock<std::mutex> lock(mtx); }
|
手动解锁
1 2 3 4 5 6 7 8
| std::mutex mtx; void func() { std::unique_lock<std::mutex> lock(mtx); lock.unlock(); }
|
延迟加锁
1 2 3 4 5 6 7
| std::mutex mtx; void func() { std::unique_lock<std::mutex> lock(mtx, std::defer_lock); lock.lock(); }
|
尝试加锁
1 2 3 4 5 6 7 8 9
| std::mutex mtx; void func() { std::unique_lock<std::mutex> lock(mtx, std::try_to_lock); if (lock.owns_lock()) { } else { } }
|
互斥锁转移
1 2 3 4 5 6
| std::mutex mtx; void func() { std::unique_lock<std::mutex> lock1(mtx); std::unique_lock<std::mutex> lock2 = std::move(lock1); }
|
条件变量
生产者与消费者模型

生产者与消费者模型可以这样比喻:生产者是小鸡,任务队列是鸡蛋篮子,消费者是饲养员。有源源不断的任务从生产者发出,由消费者解除,也类似于银行排队系统。
当任务队列为空的时候,消费者无法去取任务,因此会进入等待的状态。那此时老板会下发任务,如何让消费者知道有任务?需要通知,让消费者知道我该往里面取任务了。
condition_variable有两种
- notify_one 唤醒消费者中的一个线程来干活
- notify-all 唤醒消费者中的所有线程来干活
区别在于:通知只发一次,但是对象不同,唤醒一条线程和所有线程的区别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include <iostream> #include <mutex> #include <thread> #include <queue> #include <condition_variable>
std::queue<int>g_queue; std::condition_variable g_cv; std::mutex mtx;
void Producer() { for (int i = 0; i < 10; i++) { std::unique_lock<std::mutex> lock(mtx); g_queue.push(i); g_cv.notify_one(); std::cout << "Producer : " << i << std::endl; } std::this_thread::sleep_for(std::chrono::microseconds(100)); }
void Consumer() { while (1) { std::unique_lock<std::mutex> lock(mtx); g_cv.wait(lock, []() {return !g_queue.empty(); }); int value = g_queue.front(); g_queue.pop();
std::cout << "Consumer : " << value << std::endl; } }
int main() { std::thread t1(Producer); std::thread t2(Consumer); t1.join(); t2.join(); return 0; }
|
wait函数g_cv.wait(lock, predicate)的作用:
- 当前线程进入等待状态,直到 predicate 返回 true,也就是说第二个判断条件是true就往下执行
- lock 是一个 std::unique_lockstd::mutex,用于保护临界区资源。
- predicate 是一个 Lambda 表达式,返回 true 时线程继续执行,否则会一直等待。
两个代码是等价的
1
| g_cv.wait(lock, []() { return !g_queue.empty(); });
|
1 2 3 4 5
| while (!g_queue.empty()) { g_cv.wait(lock); }
|
- 当 g_queue 为空时,线程会阻塞(等待)。
- 当 g_queue 非空时,线程继续执行,不会进入等待状态。
原子操作
除了可以用互斥锁来维护共享变量外,还可以通过原子操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include <iostream> #include <thread> #include <atomic> std::atomic <int> a(0); void func() { for (int i = 0; i < 1000000; i++) { a += 1; } }
int main() { std::thread t1(func); std::thread t2(func); t1.join(); t2.join(); std::cout << a << std::endl; system("pause"); return 0; }
|
注意,在初始化的时候,不允许使用std::atomic<int> a = 0
,因为原子操作不允许拷贝复制,应该用默认的构造函数std::atomic<int> a(0)或std::atomic<int> a{0}
把共享的数据设置为原子变量,更好地维护线程安全,还可以提升运行速度。
小班演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| #include <iostream> #include <mutex> #include <condition_variable> #include <queue> #include <chrono>
std::mutex mtx; std::condition_variable g_cv; std::queue<int> g_queue; int flag = 0;
void Producer() { for (int i = 0; i < 100; i++) { std::unique_lock<std::mutex> lock(mtx); g_queue.push(i); g_cv.notify_one(); std::cout << "Producer : " << i << std::endl; } std::this_thread::sleep_for(std::chrono::microseconds(1000)); }
void Comsumer() { while (1) { if (flag == 100) break; std::unique_lock<std::mutex> lock(mtx); g_cv.wait(lock, []() {return !g_queue.empty(); }); flag++; int value = g_queue.front(); g_queue.pop(); std::cout << "Comsumer : " << value << std::endl; } }
int main() { std::thread t1(Producer); std::thread t2(Comsumer); t1.join(); t2.join(); return 0; }
|
C++11跨平台线程池