Linux线程互斥学习笔记--详细分析
一、互斥锁
为啥要有互斥?
多个进程/线程执行的先后顺序不确定,何时切出CPU也不确定。
多个进程/线程访问变量的动作往往不是原子的。
1. 操作步骤
(1)创建锁
// 创建互斥锁mutex pthread_mutex_t mutex;
(2)初始化锁
在Linux下, 线程的互斥量数据类型是pthread_mutex_t 在使用前, 要对它进行初始化:
初始化的两种方法:(推荐使用第二种)
1.静态分配
pthread_mutex mutex = PTHREAD_MUTEX_INITIALIZER;
2.动态分配
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
mutex: 要初始化的互斥量(restrict的作用是告诉调用者,不要改变指针的指向)
attr:锁的属性,一般写NULL
加restrict的作用:只用于修饰函数参数里的指针,这个指针会频繁使用,所以把这个地址放到寄存器里,用着好找。
①设置线程的属性
int pthread_attr_init(pthread_attr_t *attr);//初始化线程属性 int pthread_attr_destroy(pthread_attr_t *attr);//销毁线程属性
Thread attributes(线程属性):
线程的分离属性: Detach state=PTHREAD_CREATE_DETACHED
线程的竞争范围: Scope = PTHREAD_SCOPE_SYSTEM
是否继承调度策略: Inherit scheduler = PTHREAD_EXPLICIT_SCHED
调度策略: Scheduling policy = SCHED_OTHER
调度优先级: Scheduling priority = 0
线程栈之间的保留区域: Guard size = 4096 bytes
自己指定栈地址: Stack address = 0x40197000
栈大小: Stack size = 0x3000000 bytes
//设置线程的分离属性 int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); //detachstate:有以下两种选择 PTHREAD_CREATE_DETACHED:设置成分离态 PTHREAD_CREATE_JOINABLE:设置成可结合态 //获取线程的分离属性 int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate); //int *detachstate:输出型参数,将分离属性存放在该变量里
(3)上锁 && 解锁
对共享资源的访问, 要对互斥量进行加锁, 如果互斥量已经上了锁, 调用线程会阻塞, 直到互斥量被解锁. 在完成了对共享资源的访问后,要对互斥量进行解锁。
具体说一下trylock函数, 这个函数是非阻塞调用模式, 也就是说, 如果互斥量没被锁住, trylock函数将把互斥量加锁, 并获得对共享资源的访问权限; 如果互斥量被锁住了, trylock函数将不会阻塞等待而直接返回EBUSY, 表示共享资源处于忙状态。
锁粒度(尽量小):临界区操作个数,执行时间的长短
int pthread_mutex_lock(pthread_mutex_t *mutex); //加锁:如果是1,置0,返回 // 如果是0,阻塞 int pthread_mutex_trylock(pthread_mutex_t *mutex);//非阻塞加锁 int pthread_mutex_unlock(pthread_mutex_t *mutex); //解锁
返回值: 成功则返回0, 出错则返回错误编号.
(4)销毁互斥锁
对于动态分配的互斥量, 在申请内存(malloc)之后, 通过pthread_mutex_init进行初始化, 并且在释放内存(free)前需要调用pthread_mutex_destroy.
注意:
使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量不需要销毁
不要销毁一个已经加锁的互斥量
已销毁的互斥量要确保后面不会有线程尝试加锁
pthread_mutex_destroy(&lock); //销毁
返回值: 成功则返回0, 出错则返回错误编号.
说明: 如果使用默认的属性初始化互斥量, 只需把attr设为NULL. 其他值在以后讲解。
2. 死锁
(1)死锁的两种情况:
情况1:
如果两个线程先后调用两次lock,第二次调用lock时,由于锁已被占用,该线程会挂起等待别的线程释放锁,然后锁正是被自己占用着的,该线程又被挂起不能释放锁,因此就永远处于挂起等待状态了,这就叫死锁。
情况2:
有线程A、B。A获得锁1,B获得锁2,此时A调用lock企图获得锁2,结果是需要挂起等待B释放锁2,而此时B也调用了lock企图获得锁1,结果是B挂起等待A释放锁1,于是乎A、B永远处于挂起状态。
(2)避免的死锁的原则
死锁主要发生在有多个依赖锁存在时,会在一个线程试图以与另一个线程相反顺序锁住互斥量时发生.如何避免死锁是使用互斥量应该格外注意的东西。
总体来讲, 有几个不成文的基本原则:
对共享资源操作前一定要获得锁。
完成操作以后一定要释放锁。
尽量短时间地占用锁。
如果有多锁, 如获得顺序是ABC连环扣, 释放顺序也应该是ABC。
线程错误返回时应该释放它所获得的锁。
写程序是尽量避免同时获得多个锁,如果一定要这么做,则遵循一个原则:如果所有线程在需要多个锁时都按相同的先后顺序(常见是按mutex变量的地址顺序)获得锁,则不会出现死锁。
mutex互斥信号量锁住的不是一个变量,而是阻塞住一段程序。如果对一个mutex变量testlock,执行了第一次pthread_mutex_lock(testlock)之后,在unlock之前的这段时间内,如果有其他线程也执行到了pthread_mutex_lock,这个线程就会阻塞住,直到之前的线程unlock之后才能执行,由此,实现同步,也就达到保护临界区资源的目的。
为了实现互斥操作,大多数体系结构提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据交换,由于只有一条指令,保证了原子性。即使是多处理器平台,访问内存的总线周期也有先后,一个处理器的交换指令执行时另一个处理器的交换指令只能等待总线周期。
(3)临界区代码原则
短——临界区代码简洁明了;
平——临界区代码逻辑清晰,没有复杂的函数调用尤其是尽量不要申请其他互斥资源;
快:临界区代码执行速度快。
3. 互斥锁和信号量的区别
互斥量用于线程的互斥,信号线用于线程的同步。
这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。
互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源
互斥量值只能为0/1,信号量值可以为非负整数。
也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。
互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。
4. 线程安全和可重入
可重入函数:在多个执行流中被同时调用不会存在问题。
线程安全函数:在多线程中被同时调用不会存在问题。
可重入函数一般情况下都是线程安全的
线程安全函数不一定是可重入函数
二、自旋锁
1. 操作步骤
//1. 定义自旋锁 pthread_spinlock_t spin; //2. 初始化自旋锁 int pthread_spin_init(pthread_spinlock_t *lock, int pshared); //3. 上锁 int pthread_spin_lock(pthread_spinlock_t *lock); int pthread_spin_trylock(pthread_spinlock_t *lock); //4. 解锁 int pthread_spin_unlock(pthread_spinlock_t *lock); //5. 销毁锁 int pthread_spin_destroy(pthread_spinlock_t *lock);
2. 自旋锁和互斥锁的区别
互斥锁是当阻塞在pthread_mutex_lock时,放弃CPU,好让别人使用CPU。自旋锁阻塞在pthread_spin_lock时,不会释放CPU,不断向cup询问可以用了不。
三、读写锁
1. 读写锁的规则
读读共享
读写排他
写写排他
写优先级高
2. 操作步骤
// 1. 定义锁 pthread_rwlock_t lock; // 2. 初始化 pthread_rwlock_init(&lock, NULL); // 3. 读锁 pthread_rwlock_rdlock(&lock); pthread_rwlock_wrlock(&lock); // 4. 解锁 pthread_rwlock_unlock(&lock); // 5. 销毁锁 pthread_rwlock_destroy(&lock);
3. 代码实现
#include #include #include #include int g_count; pthread_rwlock_t rw; void* route_read(void* arg) { int id = *(int*)arg; free(arg); while(1) { pthread_rwlock_rdlock(&rw); printf("%d read_pthread : %d\n", id, g_count); pthread_rwlock_unlock(&rw); usleep(100000); } } void* route_write(void* arg) { int id = *(int*)arg; free(arg); while(1) { pthread_rwlock_wrlock(&rw); printf("%d write_pthread : %d\n", id, ++g_count); pthread_rwlock_unlock(&rw); usleep(100000); } } int main() { pthread_t w1, r1, r2, r3; pthread_rwlock_init(&rw, NULL); int* p = (int*)malloc(sizeof(int)); *p = 1; pthread_create(&w1, NULL, route_write, p); int* p1 = (int*)malloc(sizeof(int)); *p1 = 1; pthread_create(&r1, NULL, route_read, p1); int* p2 = (int*)malloc(sizeof(int)); *p2 = 2; pthread_create(&r2, NULL, route_read, p2); int* p3 = (int*)malloc(sizeof(int)); *p3 = 3; pthread_create(&r3, NULL, route_read, p3); pthread_join(w1, NULL); pthread_join(r1, NULL); pthread_join(r2, NULL); pthread_join(r3, NULL); pthread_rwlock_destroy(&rw); }
、Linux线程互斥的视频资料
http://www.makeru.com.cn/live/1392_715.html?s=45051
可以加vx获取更多有用的Linux学习资料
Linux线程互斥学习笔记--详细分析的更多相关文章
- Linux下iptables学习笔记
Linux下iptables学习笔记 在Centos7版本之后,防火墙应用已经由从前的iptables转变为firewall这款应用了.但是,当今绝大多数的Linux版本(特别是企业中)还是使用的6. ...
- Linux内核OOM机制的详细分析(转)
Linux 内核 有个机制叫OOM killer(Out-Of-Memory killer),该机制会监控那些占用内存过大,尤其是瞬间很快消耗大量内存的进程,为了 防止内存耗尽而内核会把该进程杀掉.典 ...
- Linux进程间通信IPC学习笔记之同步二(SVR4 信号量)
Linux进程间通信IPC学习笔记之同步二(SVR4 信号量)
- Linux进程间通信IPC学习笔记之同步二(Posix 信号量)
Linux进程间通信IPC学习笔记之同步二(Posix 信号量)
- Linux进程间通信IPC学习笔记之消息队列(SVR4)
Linux进程间通信IPC学习笔记之消息队列(SVR4)
- Linux防火墙iptables学习笔记(三)iptables命令详解和举例[转载]
Linux防火墙iptables学习笔记(三)iptables命令详解和举例 2008-10-16 23:45:46 转载 网上看到这个配置讲解得还比较易懂,就转过来了,大家一起看下,希望对您工作能 ...
- linux基础命令学习笔记(二)
linux基础命令学习笔记(二) 1.kill :终止进程 kill pid (唯一标示一个进程) kill -9 强制终止 kill -15 命令未结束不能终止 # ps aux 查看所有进程 ...
- 兄弟连Linux运维学习笔记
最新经典linux运维兄弟连Linux运维学习笔记... --------------- 全程1.5倍播放.加油我一定可以学完Linux----------------------Unix与Linux ...
- Linux shell 菜鸟学习笔记....
20171123 Linux shell 基础学习笔记1. shell 的开始 一般是 #!/bin/bash 通过 #! 来唯一指定使用的shell路径 其他的 # 都表示注释.2. shell 的 ...
随机推荐
- FastDFS 配置 Nginx 模块及访问测试
#备注:以下nginx-1.10.3源码目录根据nginx版本号不同会有相应的变化,以nginx版本号为准#一.安装 Nginx 和 fastdfs-nginx-module1,安装 Nginx 请看 ...
- 在excel中,应用公式到多行
当一个单元格中输入公式后, 选中单元格 然后将鼠标放到右下角的控制手柄处,当鼠标变成"黑十字"标志 双击鼠标左键 即可
- Jenkins教程(七)实现 GitLab 提交/合并代码触发构建
楔子 最近公司推行统一构建平台(基于 Jenkins + Kubernetes 插件创建 slave),原来部门自建的 Jenkins 不让用了. 迁移上统一构建平台的最大阻力是前端模块发布的问题: ...
- Centos8.X 搭建Prometheus+node_exporter+Grafana实时监控平台
Prometheus Promtheus是一个时间序列数据库,其采集的数据会以文件的形式存储在本地中,因此项目目录下需要一个data目录,需要我们自己创建,下面会讲到 下载 下载好的.tar.gz包放 ...
- kubeadm 如何将节点加入集群
kubeadm join 使用 token 过期之后(24小时过期),如何加入集群 一.重启生成新token # 创建新token kubeadm token create # 查看是否存在有效的 t ...
- Visaul Studio Code中提示 vue:无法加载vue.ps1,未对vue.ps1进行数字签名
Visaul Studio Code错误提示 错误如图: 解决办法 首先以管理员身份打开windows PowShell终端. 输入下面命令,如提示选择Y即可. get-help set-execut ...
- DIVIDEMIX: LEARNING WITH NOISY LABELS AS SEMI-SUPERVISED LEARNING
论文阅读: DIVIDEMIX: LEARNING WITH NOISY LABELS AS SEMI-SUPERVISED LEARNING 作者说明 版权声明:本文为博主原创文章,遵循CC 4.0 ...
- The art of multipropcessor programming 读书笔记-硬件基础1
本系列是 The art of multipropcessor programming 的读书笔记,在原版图书的基础上,结合 OpenJDK 11 以上的版本的代码进行理解和实现.并根据个人的查资料以 ...
- NOIP模拟74
前言 我就想说一句,T3 给了一个什么牛马大样例!!!!!!!!,气\(^{TM}\)死我!!!!!!! 我的 \(\mathcal{O}(n)\) 算法始终过不掉大样例我 TM ,要不然我就直接上矩 ...
- 极简SpringBoot指南-Chapter01-如何用Spring框架声明Bean
仓库地址 w4ngzhen/springboot-simple-guide: This is a project that guides SpringBoot users to get started ...