互斥锁的相关知识及应用

描述

在开发某段代码时,需要加互斥锁,这里进行相关知识的总结

基础知识

1. 原子性与原子操作

原子性

原子性是指一个操作是不可中断的。

(这里有一个非我专业领域的名词, CPU指令集中常见的一个指令:CAS。它完成两个操作,一个比较,一个交换,后一个完不完成依赖于前一个操作的结果,从逻辑上说,它们是两个操作,可是它们特意被实现为不可打断的。这就是一个经典的原子指令)

原子操作

原子操作是指不会被线程调度机制打断的操作。这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程) 。原子操作是不可分割的,在执行完毕之前不会被任何其它任务或事件中断

进一步举例子

int a = 10; 
a++;
int b=a; 
a = a+1;

上述只有第一个是原子操作,剩下三个都不是原子操作。比如a++需要三步,读取a的值,值+1,再把值赋给a。

为什么要提原子性

因为总有一些代码,我想让它被某个线程执行的时候,我不想让其他线程来改变我这段代码中间的变量值。这种想法专业的说,就是,我想让这段代码具有原子性,称为一个原子操作。

这也是我为什么要先介绍原子性的原因。

2. 互斥锁

互斥锁是什么?

互斥,是指一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。

互斥锁,是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即上锁( lock )和解锁( unlock )。

互斥锁也可以称为互斥量。互斥量是一个可以处于两态之一的变量:解锁和加锁

互斥锁的特点

  1. 原子性:把一对互斥锁定为一个原子操作,这意味着操作系统(或pthread函数库)保证了如果一个线程锁定了一个互斥量,没有其他线程在同一时间可以成功锁定这个互斥量;(我对这段代码加锁后,其他线程都得等我这个线程运行完这段代码再说

  2. 唯一性:如果一个线程锁定了一个互斥量,在它解除锁定之前,没有其他线程可以锁定这个互斥量;(我对这段代码加锁后,我这个线程不解锁,其他线程都不可以加锁

  3. 非繁忙等待:如果一个线程已经锁定了一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何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()

这个请查看我的另外一篇博客

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值