Linux线程(三)
Linux线程(三)
文章目录
- Linux线程(三)
- 一、互斥量
- 二、.互斥量的接口
- 三、互斥量实现用原理探究
- 四、可重入VS线程安全
- 五、死锁
一、互斥量
根据前面的分析,得到的结果不是我们想要的原因是–ticket操作不是原子操作,这个共享资源可能并发的切换大其他线程,导致有多个线程同时影响到这个共享资源,所以导致得到的结果不对。
- 1.解决方法(加锁—>Linux中叫这把锁为互斥量):
代码必须有互斥行为:当有一个执行流(有一个线程)进入临界区时,不允许其他线程进入
该临界区- 如果多个线程同时要求执行临界区的代码,并且临界区内没有线程在执行,那么
只允许一个线程进入该临界区
- 如果线程不在临界区内执行,那么该线程不能阻止其他线程进入临界区
二、.互斥量的接口
- 1.初始化互斥量
- 方法一:静态分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
- 方法二:动态分配
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
- 功能:初始化互斥量
- 参数:
pthread_mutex_t *restrict mutex:要初始化的互斥量
const pthread_mutexattr_t *restrict attr:指定了新建互斥锁的属性。如果参数attr为NULL,则使用默认的互斥锁属性,默认属性为快速互斥锁
- 返回值:成功返回0,失败返回错误码
- 2.销毁互斥量:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
- 功能:是销毁一个互斥量
- 参数:要销毁的互斥量
- 注意⚠️:使用PTHREAD_MUTEX_INITIALIZER(静态初始化的互斥量)的互斥量不需要销毁
- 不要销毁一个已经加锁的互斥量(即销毁前要释放锁)
- 已经销毁的互斥量,要确保后面不会有线程去尝试对其加锁
- 3.对互斥量加锁和解锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex)
调用pthread_mutex_lock可能会遇到以下几种情况
互斥量处于未锁状态,该函数将对该互斥量加锁,同时返回成功
发起调用pthread_mutex_lock函数时,其他线程已经把互斥量加完锁,或者存在其他线程同时申请对同一个互斥量进行加锁,但自己没有成功竞争到互斥量,那么pthread_mutex_lock调用就会陷入阻塞(即执行流被挂起),直到成功竞争到互斥量的线程释放锁,该执行流才可能解除阻塞
注意:
互斥锁pthread_mutex也叫“挂起等待锁”,一旦线程获取锁失败,就会挂起到操作系统的一个等待队列中,这个获取锁失败而进入挂起等待队列的线程具体什么时候恢复执行也是不一定的,也不是获取到锁的线程释放锁后立即能执行的,因为线程的万恶之源”==抢占式==“
。4.改进前面的代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>int ticket = 100;
pthread_mutex_t mutex;
void *route(void *arg)
{char *id = (char*)arg;while ( 1 ) {pthread_mutex_lock(&mutex);if ( ticket > 0 ) {usleep(1000);printf("%s sells ticket:%d\n", id, ticket);ticket--;pthread_mutex_unlock(&mutex);// sched_yield(); 放弃CPU} else {pthread_mutex_unlock(&mutex);break;}}
} int main( void )
{pthread_t t1, t2, t3, t4;pthread_mutex_init(&mutex, NULL);pthread_create(&t1, NULL, route, "thread 1");pthread_create(&t2, NULL, route, "thread 2");pthread_create(&t3, NULL, route, "thread 3");pthread_create(&t4, NULL, route, "thread 4");pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);pthread_mutex_destroy(&mutex);
}
从上面的结果看现在的代码已经是正确的了。
- 5.互斥锁的缺陷:
互斥锁虽然能够保证线程安全,但是最终导致程序的效率会受到影响,并且还有产生死锁的风险
- 产生死锁的场景:a.一个线程进行加锁后,再次尝试对同一个临界资源加锁 b.多个线程多把锁极易产生死锁
三、互斥量实现用原理探究
- 经过上面的例子,我们已经知道单纯的++ / – 操作都不是原子的,有可能会导致数据一致性问题
- 为了实现互斥锁操作,大
多数体系都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据进行交换,由于只有一条指令,保证了原子性
,即使是多处理器平台,访问内存的总线周期也有先后,一个处理器上的交换指令执行时也只能等待另一个处理器的交换指令总线周期执行结束。
四、可重入VS线程安全
- 1.比较
可重入 | 线程安全 |
---|---|
重入是指同一个函数被不同的执行流调用时,前一个执行流的流程还未结束,就有其他的执行流再次进入函数。一个函数在从重的情况下,运行结果不会出现任何不同或者没有任何执行问题,则称该函数为可重入函数,反之为不可重入函数 | 线程安全是指多个线程并发执行同一段代码,不会出现不同的结果或不会出现执行问题。其中如果对全局变量或对静态变量进行操作,并且没有锁的保护的情况下,就有可能出现线程安全问题 |
注意:⚠️:可重入包含了线程安全问题。即如果一个函数可重入就一定线程安全,反之如果一个函数是线程安全的就不一定是可重入的
- 2.常见的线程不安全的情况:
- 不保护共享变量的函数
- 函数状态随着被调用,状态发生变化的函数
- 返回指向静态变量指针的函数
- 调用线程不安全函数的函数
- 3.常见的线程安全的情况:
- 每个线程对全局变量或静态变量只有读取的权限而没有写入的权限,一般来说这些线程是线程安全的
- 类或者接口对于线程来说都是原子操作
- 多个线程之间的切换不会导致该接口的执行结果出现二义性
- 4.常见的不可重入的情况:
- 调用了malloc/free函数,因为malloc是用全局链来管理堆的
- 调用了标准IO库函数,标准IO库的很多实现都以不可重入的方式使用全局数据结构的
- 可重入函数体内使用了静态的数据结构
- 5.常见可重入情况:
- 不使用全局变量或静态变量
- 不使用malloc或者new开辟出的空间
- 不调用不可重入函数
- 不返回静态或全局数据,所有的数据都有函数的调用者提供
- 使用本地数据,或者使用通过制作全局数据的本地拷贝来保护全局数据
- 6.可重入与线程安全的联系:
函数是可重入的就是线程安全的
函数是不可重入的,有可能会有线程安全问题
- 如果一个函数内有全局变量/静态数据等,那么这个函数既不是线程安全的也不是可重入的
- 7.可重入与线程安全的区别:
可重入函数是线程安全函数的一种
- 线程安全不一定可重入,而可重入函数一定是线程安全函数
- 如果将对临界资源的访问加上锁,这这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的
五、死锁
- 1.死锁:死锁是指在一组进程中的各个进程均占有不会释放的资源,但因为互相申请被其他进程所占用的不会被释放的资源而处于一种永久等待的状态
- 2,产生死锁的四个必要条件
- 互斥条件:一个资源每次只能被一个执行流使用
- 请求与保持的条件:一个执行流因请求资源而阻塞时,要对已获得资源的进程进行请求保持不放
- 不剥夺条件:一个执行流已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干个执行流之间形成一种头尾相接的循环等待资源的关系
- 3.避免死锁
- 加锁的顺序要一致
- 避免锁未释放的场景
- 资源一次性分配
- 让临界区代码尽可能的短一些
- 让临界区代码不要调用复杂的函数
- 让临界区的代码尽量快的执行结束
- 4.避免死锁的算法
- 死锁检查算法
- 银行家算法
Linux线程(三)相关推荐
- 50.Linux 线程三 同步
Linux系统中常用实现线程同步的方式有三种,分别为互斥锁.条件变量与信号量. 下面将对这三种方式逐一进行讲解. 4.1 互斥锁 使用互斥锁实现线程同步时,系统会为共享资源添加一个称为互斥锁的标记,防 ...
- [转载]Linux 线程实现机制分析
自从多线程编程的概念出现在 Linux 中以来,Linux 多线应用的发展总是与两个问题脱不开干系:兼容性.效率.本文从线程模型入手,通过分析目前 Linux 平台上最流行的 LinuxThreads ...
- Linux 线程的创建与同步
Linux 线程的创建与同步 1.线程的定义 2.线程的创建和使用 3.理解线程的并发运行 3.线程同步 3.线程的实现 1.线程的定义 线程:进程内部的一条执行路径.是资源调度和执行的基本单位. 进 ...
- linux线程的实现【转】
转自:http://www.cnblogs.com/zhaoyl/p/3620204.html 首先从OS设计原理上阐明三种线程:内核线程.轻量级进程.用户线程 内核线程 内核线程就是内核的分身,一个 ...
- linux 线程 进程经典文章
进程是程 序在计算机上的一次执行活动.当你运行一个程序,你就启动了一个进程.显然,程序是 死的(静态的),进程是活的(动态的).进程可以分为系统进程和用户进程.凡是用于完成操作系统的各种功能的进程就是 ...
- linux 线程--内核线程、用户线程实现方法
Linux上进程分3种,内核线程(或者叫核心进程).用户进程.用户线程 内核线程拥有 进程描述符.PID.进程正文段.核心堆栈 当和用户进程拥有相同的static_prio 时,内核线程有机会得到更多 ...
- Linux 线程与进程,以及通信
http://blog.chinaunix.net/uid-25324849-id-3110075.html 部分转自:http://blog.chinaunix.net/uid-20620288-i ...
- Linux线程-互斥锁pthread_mutex_t
Linux线程-互斥锁pthread_mutex_t 在线程实际运行过程中,我们经常需要多个线程保持同步.这时可以用互斥锁来完成任务:互斥锁的使用过程中,主要有pthread_mutex_init, ...
- c++ linux 线程等待与唤醒_Linux线程同步(互斥量、信号量、条件变量、生产消费者模型)...
为什么要线程同步? 线程间有很多共享资源,都对一个共享数据读写操作,线程操作共享资源的先后顺序不确定,可能会造成数据的冲突 看一个例子 两个线程屏行对全局变量count++ (采用一个val值作为中间 ...
最新文章
- python3+scapy扫描获取局域网主机ip和mac
- LINKs: Xamarin.Forms + Prism
- 【技术系列】浅谈GPU虚拟化技术(第一章)
- Acwing第 1 场周赛【完结】
- 四十七、Ansible自动化入门
- python3爬虫初探(五)之从爬取到保存
- 数组名与指向数组的指针之间的联系与区别【数据结构】
- Python基础语法毕业笔记-最简单的添加删除程序
- C/C++语言函数参数里的“...”作用,va_list的使用(stdarg.h)
- C#LeetCode刷题之#475-供暖器(Heaters)
- leetcode 151 python
- 树状数组 Binary Indexed Tree/Fenwick Tree
- jQuery - 获取内容和属性
- python+HEG对mod021km数据进行几何校正、辐射定标
- 数据挖掘论文matlab,数据挖掘论文3000字范文参考
- 系统重启-------即java代码重启tomcat!
- Multisim14简介与安装
- C#实现文本语音播放
- 微信上老师发的试卷怎样打印?
- 全网最详细elasticsearch7.10.2安装手册
热门文章
- 正确率 精度 召回率 错误率
- JavaScript正则表达式语法与示例
- 2016云栖大会马云畅谈未来五大创新趋势
- 基于SpringMVC、Maven以及Mybatis的环境搭建 【转】
- php -- 取整数
- 23种设计模式的有趣见解 .
- 上海理工大学第二届“联想杯”全国程序设计邀请赛 - Experiment Class(几何+三分套三分)
- CodeForces - 1288E Messenger Simulator(树状数组)
- POJ - 3080 Blue Jeans(暴力+KMP)
- linux安装tensorflow教程,Ubuntu 16.04 安装 TensorFlow(GPU支持)