C11线程管理:互斥锁

时间:2023-01-03 21:02:51

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)&lowbar;进程与线程中的lock(线程中互斥锁、递归锁、信号量、Event对象、队列queue)

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

  4. linux线程及互斥锁

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

  5. Linux 线程编程2&period;0——线程同步-互斥锁

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

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

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

  7. Python中线程与互斥锁

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

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

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

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

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

随机推荐

  1. windows8建立局域网的方法

    win8建立局域网的方法:1.首先笔记本有无线网卡且支持 虚拟WIFI ;2.按win+X键,选择"命令提示符(管理员)A"; 3.输入"netsh wlan set h ...

  2. mysql存储过程、函数和触发器的创建 &lbrack;转&rsqb;

    http://blog.itpub.net/559237/viewspace-438942/ 今天花了半天时间来研究mysql的存储过程函数和触发器的创建,觉得和oracle的大同小异,只是语法上更艰 ...

  3. LIBRARY&lowbar;PATH和LD&lowbar;LIBRARY&lowbar;PATH环境变量的区别

    LIBRARY_PATH和LD_LIBRARY_PATH是Linux下的两个环境变量,二者的含义和作用分别如下: LIBRARY_PATH环境变量用于在程序编译期间查找动态链接库时指定查找共享库的路径 ...

  4. Hive 时间日期处理总结

    最近用hive比较多,虽然效率低,但是由于都是T+1的业务模式.所以也就不要求太多了,够用就行.其中用的吧比较多就是时间了,由于大数据中很多字段都不是标准的时间类型字段,所以期间涉及了很多的时间日期字 ...

  5. python str与bytes之间的转换

    # bytes object b = b"example" # str object s = "example" # str to bytes bytes(s, ...

  6. 进程间通信——XSI IPC之消息队列

    进程间通信XSI IPC有3种:消息队列.共享内存.信号量.它们之间有很多相似之处,但也有各自的特殊的地方.消息队列作为其中比较简单的一种,它会有些什么东西呢,来一起探讨探讨.. 消息队列结构 消息队 ...

  7. Java开发笔记(十七)各得其所的多路分支

    前面提到条件语句的标准格式为“if (条件) { /* 条件成立时的操作代码 */ } else { /* 条件不成立时的操作代码 */ }”,乍看之下仿佛只有两个分支,一个是条件成立时的分支,另一个 ...

  8. thinkphp5实现多级控制器

    默认情况下目录结构 application ->admin->controller->class.php 当项目比较多的时候,目录下控制器文件较多,考虑按模块增加一层目录 appli ...

  9. Day27--Python--初识socket

    一. C\S架构, 客户端服务端架构 客户端(client): 享受服务端提供的服务 服务端(server): 给客户端提供服务 B\S 浏览器和服务端 B (browser) 二. 网络通信的整个流 ...

  10. &lbrack;转帖&rsqb;kubeadm 实现细节

    kubeadm 实现细节 http://docs.kubernetes.org.cn/829.html 1 核心设计原则 2 常量和众所周知的值和路径 3 kubeadm init 工作流程内部设计 ...