描述
在开发某段代码时,需要加互斥锁,这里进行相关知识的总结
基础知识
1. 原子性与原子操作
原子性
原子性是指一个操作是不可中断的。
(这里有一个非我专业领域的名词, CPU指令集中常见的一个指令:CAS。它完成两个操作,一个比较,一个交换,后一个完不完成依赖于前一个操作的结果,从逻辑上说,它们是两个操作,可是它们特意被实现为不可打断的。这就是一个经典的原子指令)
原子操作
原子操作是指不会被线程调度机制打断的操作。这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程) 。原子操作是不可分割的,在执行完毕之前不会被任何其它任务或事件中断
进一步举例子
int a = 10;
a++;
int b=a;
a = a+1;
上述只有第一个是原子操作,剩下三个都不是原子操作。比如a++
需要三步,读取a的值,值+1,再把值赋给a。
为什么要提原子性
因为总有一些代码,我想让它被某个线程执行的时候,我不想让其他线程来改变我这段代码中间的变量值。这种想法专业的说,就是,我想让这段代码具有原子性,称为一个原子操作。
这也是我为什么要先介绍原子性的原因。
2. 互斥锁
互斥锁是什么?
互斥,是指一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。
互斥锁,是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即上锁( lock )和解锁( unlock )。
互斥锁也可以称为互斥量。互斥量是一个可以处于两态之一的变量:解锁和加锁
互斥锁的特点
-
原子性:把一对互斥锁定为一个原子操作,这意味着操作系统(或pthread函数库)保证了如果一个线程锁定了一个互斥量,没有其他线程在同一时间可以成功锁定这个互斥量;(我对这段代码加锁后,其他线程都得等我这个线程运行完这段代码再说)
-
唯一性:如果一个线程锁定了一个互斥量,在它解除锁定之前,没有其他线程可以锁定这个互斥量;(我对这段代码加锁后,我这个线程不解锁,其他线程都不可以加锁)
-
非繁忙等待:如果一个线程已经锁定了一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何cpu资源),直到第一个线程解除对这个互斥量的锁定为止,第二个线程则被唤醒并继续执行,同时锁定这个互斥量。(我对这个代码加锁后,其他线程还想对它加锁时,得等我解锁了,你才能加锁)
互斥锁的作用
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
代码
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
std::mutex my_mutex;
int g_count = 0;
void Counter() {
my_mutex.lock(); // 加锁
int i = ++g_count;
std::cout << "count: " << i << std::endl;
my_mutex.unlock(); // 解锁
}
int main() {
int size = 4;
// 创建一组线程。
std::vector<std::thread> my_thread;
my_thread.reserve(size);
for (int i = 0; i < size; i++) {
my_thread.emplace_back(&Counter);
}
// 等待所有线程结束。
for (std::thread& t : my_thread) {
t.join();
}
return 0;
}
输出结果会是
count: 1
count: 2
count: 3
count: 4
如果不加锁,每次的结果都会是不同的,而且是乱的,举个例子:
count: 2
count: count: 3
count: 4
1
关于代码中出现的一些写法
1. for (std::thread& t : my_thread)
for (std::thread& t : my_thread)
这个格式是C++11的for循环新形式
C++11现在有好几种形式来完成for循环,它们都可以实现遍历,功能是一样的,这里列举在下面
- 传统for循环
#include<iostream> #include<vector> int main() { std::vector<int> arr; arr.push_back(1); arr.push_back(2); for (auto it = arr.begin(); it != arr.end(); it++) { std::cout << *it << std::endl; } return 0; }
- std::for_each形式
#include<algorithm> #include<iostream> #include<vector> void func(int n) { std::cout << n << std::endl; } int main() { std::vector<int> arr; arr.push_back(1); arr.push_back(2); std::for_each(arr.begin(), arr.end(), func); return 0; }
- C++11新型for循环
#include<iostream> #include<vector> int main() { std::vector<int> arr; arr.push_back(1); arr.push_back(2); for (auto n : arr) { std::cout << n << std::endl; } return 0; }
2. reserve() 和 emplace_back()
这个请查看我的另外一篇博客