1、概述

  锁类型

  c11提供了跨平台的线程同步手段,用来保护多线程同时访问的共享数据。
  std::mutex,最基本的 Mutex 类,独占的互斥量,不能递归使用。
  std::time_mutex,带超时的独占互斥量,不能递归使用。
  std::recursive_mutex,递归互斥量,不带超时功能。
  std::recursive_timed_mutex,带超时的递归互斥量。

  lock类型

  std::lock_guard,与 Mutex RAII 相关,方便线程对互斥量上锁。
  std::unique_lock,与 Mutex RAII 相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制。

  锁函数

  std::try_lock,尝试同时对多个互斥量上锁。
  std::lock,可以同时对多个互斥量上锁。
  std::unlock,解锁。

2、独占互斥量std::mutex

  互斥量使用都很简单,接口用法也很简单。一般是通过lock()来阻塞线程,直到获取到互斥量为止。在获取互斥量完成之后,使用unlock()来解除互斥量的占用。lock()和unlock()必须成对出现。

  std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的。

  lock(),调用线程将锁住该互斥量。线程调用该函数会发生下面 3 种情况:(1). 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。(2). 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。(3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。

  unlock(), 解锁,释放对互斥量的所有权。

  try_lock(),尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞。线程调用该函数也会出现下面 3 种情况,(1). 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock 释放互斥量。(2). 如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉。(3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。

#include <iostream>
#include <thread>
#include <chrono>
#include <mutex> std::mutex g_lock; void vFunc()
{
g_lock.lock(); std::cout << "entered thread:" << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds());
std::cout << "leave thread:" << std::this_thread::get_id() << std::endl; g_lock.unlock();
} int main()
{
std::thread t(vFunc);
std::thread t1(vFunc);
std::thread t2(vFunc); t.join();
t1.join();
t2.join(); return ;
}
//输出结果
entered thread:
leave thread:
entered thread:
leave thread:
entered thread:
leave thread:

3、std::lock_guard和std::unique_lock

  使用lock_guard和std::unique_lock可以简化lock/unlock的写法,同时也更安全,因为lock_guard使用了RAII技术,在构造分配资源,在析构释放资源,会在构造的时候自动锁定互斥量,在退出作用域之后进行析构自动解锁。所以不用担心没有解锁的情况,更加安全。

  std::lock_guard与 Mutex RAII 相关,方便线程对互斥量上锁,std::unique_lock与 Mutex RAII 相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制,它可以自由的释放mutex,而std::lock_guard需要等生命周期结束才能释放。

void vFunc()
{
std::lock_guard<std::mutex> locker(g_lock);//出作用域自动解锁
std::cout << "entered thread:" << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds());
std::cout << "leave thread:" << std::this_thread::get_id() << std::endl;
}

3、递归互斥锁std::recuisive_mutex

  同一个线程不能多次获取同一个独占互斥锁,一个线程多次获取同一个互斥锁发生死锁。

//std::mutex mutex;

void funcA()
{
std::lock_guard<std::mutex> lock(mutex);
//do something
} void funcB()
{
std::lock_guard<std::mutex> lock(mutex);
//do something
} void funcC()
{
std::lock_guard<std::mutex> lock(mutex);
funcA();
funcB();
} int main()
{
//发生死锁,funcC已经获取,无法释放,funcA无法获取
funcC(); return ;
}

  为了解决死锁的问题,C11有了递归锁std::recursive_mutex,递归锁允许一个线程多次获取该锁。

//std::recursive_mutex mutex;

void funcA()
{
std::lock_guard<std::recursive_mutex> lock(mutex);
//do something
} void funcB()
{
std::lock_guard<std::recursive_mutex> lock(mutex);
//do something
} void funcC()
{
std::lock_guard<std::recursive_mutex> lock(mutex);
funcA();
funcB();
} int main()
{
//同一个线程可以多次获取同一互斥量,不会发生死锁
funcC(); return ;
}

  需要注意的是,尽量少用递归锁,原因如下:

  允许递归互斥容易放纵复杂逻辑的产生,从而导致一些多线程同步引起的晦涩问题;
  递归锁比非递归锁效率低;
  递归锁虽然可以在同一线程多次获取,但是获取次数过多容易发生问题,引发std::system错误。

4、超时锁

  std::time_mutex是超时的独占锁,std::recursive_timed_mutex是超时的递归锁,主要用于在获取锁时增加超时等待功能,设置一个等待获取锁的时间,超时后做其他的事情。超时锁多了两个获取锁的接口,try_lock_for和try_lock_until,这两个接口用来获取互斥量的超时时间。

  try_lock_for 函数接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住(与 std::mutex 的 try_lock() 不同,try_lock 如果被调用时没有获得锁则直接返回 false),如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。

  try_lock_until函数则接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。

#include <iostream>       // std::cout
#include <chrono> // std::chrono::milliseconds
#include <thread> // std::thread
#include <mutex> // std::timed_mutex std::timed_mutex mtx; void fireworks() {
// waiting to get a lock: each thread prints "-" every 200ms:
while (!mtx.try_lock_for(std::chrono::milliseconds())) {
std::cout << "-";
}
// got a lock! - wait for 1s, then this thread prints "*"
std::this_thread::sleep_for(std::chrono::milliseconds());
std::cout << "*\n";
mtx.unlock();
} int main()
{
std::thread threads[];
// spawn 10 threads:
for (int i = ; i<; ++i)
threads[i] = std::thread(fireworks); for (auto& th : threads) th.join(); return ;
}

C11线程管理:互斥锁的更多相关文章

  1. JoinableQueue队列,线程,线程于进程的关系,使用线程,线程的特点,守护线程,线程的互斥锁,死锁问题,递归锁,信号量

    1.JoinableQueue队列 JoinableQueue([maxsize]):这就像是一个Queue对象,但是队列允许项目的使用者通知生成者项目已经被成功处理.通知进程是使用共享的信号和条件变 ...

  2. C11线程管理:条件变量

    1.简介 C11提供另外一种用于等待的同步机制,它可以阻塞一个或者多个线程,直到收到另外一个线程发出的通知或者超时,才会唤醒当前阻塞的线程.条件变量要和互斥量配合起来使用. condition_var ...

  3. Python进阶(3)_进程与线程中的lock(线程中互斥锁、递归锁、信号量、Event对象、队列queue)

    1.同步锁 (Lock) 当全局资源(counter)被抢占的情况,问题产生的原因就是没有控制多个线程对同一资源的访问,对数据造成破坏,使得线程运行的结果不可预期.这种现象称为“线程不安全”.在开发过 ...

  4. linux线程及互斥锁

    进程是资源管理的最小单元,线程是程序执行的最小单元.在操作系统的设计上,从进程演化出线程,最主要的目的就是更好的支持SMP以及减小(进程/线程)上下文切换开销. 就像进程有一个PID一样,每个线程也有 ...

  5. Linux 线程编程2.0——线程同步-互斥锁

    当我们需要控制对共享资源的存取的时候,可以用一种简单的加锁的方法来控制.我们可以创建一个读/写程序,它们共用一个共享缓冲区,使用互斥锁来控制对缓冲区的存取. 函数 pthread_mutex_init ...

  6. day34 python学习 守护进程,线程,互斥锁,信号量,生产者消费者模型,

    六 守护线程 无论是进程还是线程,都遵循:守护xxx会等待主xxx运行完毕后被销毁 需要强调的是:运行完毕并非终止运行 #1.对主进程来说,运行完毕指的是主进程代码运行完毕 #2.对主线程来说,运行完 ...

  7. Python中线程与互斥锁

    了解之前我们先了解一下什么是多任务? 概念: 几个不同的事件在同时运行就是多任务, 这样的话, 我们有牵扯到了真的多任务, 假的多任务; 并行: 真的多任务, 通过电脑的核数来确定 并发: 假的多任务 ...

  8. 并发编程 - 线程 - 1.互斥锁/2.GIL解释器锁/3.死锁与递归锁/4.信号量/5.Event事件/6.定时器

    1.互斥锁: 原理:将并行变成串行 精髓:局部串行,只针对共享数据修改 保护不同的数据就应该用不用的锁 from threading import Thread, Lock import time n ...

  9. Python 开启线程的2中方式,线程VS进程(守护线程、互斥锁)

    知识点一: 进程:资源单位 线程:才是CPU的执行单位 进程的运行: 开一个进程就意味着开一个内存空间,存数据用,产生的数据往里面丢 线程的运行: 代码的运行过程就相当于运行了一个线程 辅助理解:一座 ...

随机推荐

  1. C++ Primer Plus学习:第七章

    C++入门第七章:函数-C++的编程模块 函数的基本知识 要使用C++函数,必须完成如下工作: 提供函数定义 提供函数原型 调用函数 库函数是已经定义和编译好的函数,可使用标准库头文件提供原型. 定义 ...

  2. WPF和Expression Blend开发实例:Loading动画

    今天来点实际的,项目中可以真实使用的,一个Loading的动画,最后封装成一个控件,可以直接使用在项目中,先上图: 整个设计比较简单,就是在界面上画18个Path,然后通过动画改变OpacityMas ...

  3. 【剑指offer】Java实现(持续更新中)

    面试题3 二维数组中的查找 Leetcode--74 Search a 2D Matrix /*Java Write an efficient algorithm that searches for ...

  4. [ Selenium2 从零开始 by Bruce from http://seleniumcn.cn ] 1-8 视频集锦

    内容转自: http://blog.csdn.net/sxl0727tu/article/details/51887093\ ************************************* ...

  5. c语言----程序记录

    1.结构体写入文件,读取 #include <stdio.h> #include <string.h> #include <stdlib.h> #define ma ...

  6. 一个form表单,多个提交按钮

    技巧就是把提交的input的类型改成button!这样就可以实现多个按钮提交! 以下是案例: <form action="" id="tijiao"> ...

  7. p2 形状

    形状是物理引擎进行碰撞模拟计算的依据,是刚体最基本的属性. P2中使用Shape类来表示形状,通过刚体的addShape()方法,将形状添加到刚体中之后, 就可以随着刚体的移动.旋转不断更新,并进行碰 ...

  8. 【计算机基础】当你在浏览器中输入Google.com并且按下回车之后发生了什么?

    本文转载自:https://github.com/skyline75489/what-happens-when-zh_CN#id9 按下"g"键 接下来的内容介绍了物理键盘和系统中 ...

  9. bootstrap 中的静态模式的控制按钮上的一个坑

    在使用modal时发现,代码:<button class="btn btn-danger" data-toggle="modal" data-target ...

  10. hdu 6435 CSGO(最大曼哈顿距离)

    题目链接 Problem Description You are playing CSGO. There are n Main Weapons and m Secondary Weapons in C ...