Linux系统编程----16(线程同步,互斥量 mutex,互斥锁的相关函数,死锁,读写锁)

同步概念

所谓同步,即同时起步,协调一致。不同的对象,对“同步”的理解方式略有不同。如,设备同步,是指在两 个设备之间规定一个共同的时间参考;数据库同步,是指让两个或多个数据库内容保持一致,或者按需要部分保持 一致;文件同步,是指让两个或多个文件夹里的文件保持一致。等等
而,编程中、通信中所说的同步与生活中大家印象中的同步概念略有差异。“同”字应是指协同、协助、互相 配合。主旨在协同步调,按预定的先后次序运行。

线程同步

同步即协同步调,按预定的先后次序运行。
线程同步,指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其它线程为保证数据 一致性,不能调用该功能。

  1. 举例 1: 银行存款 5000。柜台,折:取 3000;提款机,卡:取 3000。剩余:2000

  2. 举例 2: 内存中 100 字节,线程 T1 欲填入全 1, 线程 T2 欲填入全 0。但如果 T1 执行了 50 个字节失去 cpu,T2 执行,会将 T1 写过的内容覆盖。当 T1 再次获得 cpu 继续 从失去 cpu 的位置向后写入 1,当执行结束,内存中的 100 字节,既不是全 1,也不是全 0。

    产生的现象叫做“与时间有关的错误”(time related)。为了避免这种数据混乱,线程需要同步。
    “同步”的目的,是为了避免数据混乱,解决与时间有关的错误。实际上,不仅线程间需要同步,进程间、信 号间等等都需要同步机制。
    因此,所有“多个控制流,共同操作一个共享资源”的情况,都需要同步。

数据混乱原因

  1. 资源共享(独享资源则不会)
  2. 调度随机(意味着数据访问会出现竞争)
  3. 线程间缺乏必要的同步机制
  4. 以上 3 点中,前两点不能改变,欲提高效率,传递数据,资源必须共享。只要共享资源,就一定会出现竞争。 只要存在竞争关系,数据就很容易出现混乱。
  5. 所以只能从第三点着手解决。使多个线程在访问共享资源的时候,出现互斥。

互斥量 mutex

Linux 中提供一把互斥锁 mutex(也称之为互斥量) 。
每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。

  1. 资源还是共享的,线程间也还是竞争的,
  2. 但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了。

在这里插入图片描述
但,应注意:同一时刻,只能有一个线程持有该锁。
当 A 线程对某个全局变量加锁访问,B 在访问前尝试加锁,拿不到锁,B 阻塞。C 线程不去加锁,而直接访问 该全局变量,依然能够访问,但会出现数据混乱。
所以,互斥锁实质上是操作系统提供的一把“建议锁”(又称“协同锁”),建议程序中有多线程访问共享资源 的时候使用该机制。但,并没有强制限定。
因此,即使有了 mutex,如果有线程不按规则来访问数据,依然会造成数据混乱。

主要应用函数:

  1. pthread_mutex_init 函数
  2. pthread_mutex_destroy 函数
  3. pthread_mutex_lock 函数
  4. pthread_mutex_trylock 函数
  5. pthread_mutex_unlock 函数
  6. 以上 5 个函数的返回值都是:成功返回 0, 失败返回错误号。
  7. pthread_mutex_t 类型,其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待。
  8. pthread_mutex_tmutex; 变量 mutex 只有两种取值 1、0。

pthread_mutex_init 函数

初始化一个互斥锁(互斥量)—> 初值可看作 1 int pthread_mutex_init (pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
参 1:传出参数,调用时应传 &mutex
restrict 关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。 不能通过除本指针以外的其他变量或指针修改
参 2:互斥量属性。是一个传入参数,通常传 NULL,选用默认属性(线程间共享)。 参 APUE.12.4 同步属性

  1. 静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了 static 关键字修饰),可以直接 使用宏进行初始化。e.g. pthead_mutex_tmuetx=PTHREAD_MUTEX_INITIALIZER;
  2. 动态初始化:局部变量应采用动态初始化。e.g. pthread_mutex_init(&mutex,NULL)

在这里插入图片描述

pthread_mutex_destroy 函数

销毁一个互斥锁

int	pthread_mutex_destroy(pthread_mutex_t	*mutex);

pthread_mutex_lock 函数

加锁成功。可理解为将 mutex–(或-1)

 int	pthread_mutex_lock(pthread_mutex_t	*mutex);

pthread_mutex_unlock 函数

解锁成功。可理解为将 mutex++(或+1)

 int	pthread_mutex_unlock(pthread_mutex_t	*mutex);

pthread_mutex_trylock 函数

尝试加锁

int	pthread_mutex_trylock(pthread_mutex_t	*mutex);

在这里插入图片描述

示例:子线程打印小写,主控线程打印大写
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<stdlib.h>
#include<string.h>pthread_mutex_t mutex; //锁的定义void *tfn(void *arg)
{srand(time(NULL));while(1){pthread_mutex_lock(&mutex);printf("hello");sleep(rand() % 3); /*模拟长时间操作共享资源,导致cpu易主,产生与时间>有关的错误*/printf("world\n");pthread_mutex_unlock(&mutex);sleep(rand() % 3); }   return NULL;
}int main(void)
{pthread_t tid;srand(time(NULL));pthread_mutex_init(&mutex,NULL); //mutex == 1pthread_create(&tid,NULL,tfn,NULL);while(1){pthread_mutex_lock(&mutex);printf("HELLO");sleep(rand()%3);printf("WORLD\n");pthread_mutex_unlock(&mutex);   sleep(rand() % 3);}pthread_mutex_destroy(&mutex);} 

在这里插入图片描述

  1. 定义全局互斥量,初始化 init(&m,NULL)互斥量,添加对应的 destry
  2. 两个线程 while 中,两次 printf 前后,分别加 lock 和 unlock
  3. 将 unlock 挪至第二个 sleep 后,发现交替现象很难出现。
    线程在操作完共享资源后本应该立即解锁,但修改后,线程抱着锁睡眠。睡醒解锁后又立即加锁,这两个 库函数本身不会阻塞。
    所以在这两行代码之间失去 cpu 的概率很小。因此,另外一个线程很难得到加锁的机会
  4. main 中加 flag=5 将 flg 在 while 中-- 这时,主线程输出 5 次后试图销毁锁,但子线程未将锁释放,无法 完成。
  5. main 中加 pthread_cancel()将子线程取消。
注意事项:在访问共享资源前加锁,访问结束后立即解锁。锁的“粒度”应越小越好。

死锁

  1. 线程试图对同一个互斥量 A 加锁两次。
  2. 线程 1 拥有 A 锁,请求获得 B 锁;线程 2 拥有 B 锁,请求获得 A 锁
  3. 当不能获得所有的锁时,放弃已经占有的锁

读写锁

与互斥量类似,但读写锁允许更高的并行性。其特性为:写独占,读共享。

读写锁状态

一把读写锁具备三种状态:

  1. 读模式下加锁状态 (读锁)
  2. 写模式下加锁状态 (写锁)
  3. 不加锁状态

读写锁特性:

  1. 读写锁是“写模式加锁”时, 解锁前,所有对该锁加锁的线程都会被阻塞。
  2. 读写锁是“读模式加锁”时, 如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。
  3. 读写锁是“读模式加锁”时, 既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁 会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高
  4. 读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独 占模式锁住的。写独占、读共享
  5. 读写锁非常适合于对数据结构读的次数远大于写的情况。

主要应用函数:

  1. pthread_rwlock_init 函数
  2. pthread_rwlock_destroy 函数
  3. pthread_rwlock_rdlock 函数
  4. pthread_rwlock_wrlock 函数
  5. pthread_rwlock_tryrdlock 函数
  6. pthread_rwlock_trywrlock 函数
  7. pthread_rwlock_unlock 函数
  8. 以上 7 个函数的返回值都是:成功返回 0, 失败直接返回错误号。
  9. pthread_rwlock_t 类型 用于定义一个读写锁变量。
  10. pthread_rwlock_trwlock;

pthread_rwlock_init 函数

初始化一把读写锁
int pthread_rwlock_init(pthread_rwlock_t *restrictrwlock,const pthread_rwlockattr_t *restrictattr);
参 2:attr 表读写锁属性,通常使用默认属性,传 NULL 即可。

pthread_rwlock_destroy 函数

销毁一把读写锁

int	pthread_rwlock_destroy(pthread_rwlock_t	*rwlock);
pthread_rwlock_rdlock 函数

以读方式请求读写锁。(常简称为:请求读锁)

int	pthread_rwlock_rdlock(pthread_rwlock_t	*rwlock);
pthread_rwlock_wrlock 函数

以写方式请求读写锁。(常简称为:请求写锁)

int	pthread_rwlock_wrlock(pthread_rwlock_t	*rwlock);
pthread_rwlock_unlock 函数

解锁

int	pthread_rwlock_unlock(pthread_rwlock_t		*rwlock);
pthread_rwlock_tryrdlock 函数

非阻塞以读方式请求读写锁(非阻塞请求读锁)

int	pthread_rwlock_tryrdlock(pthread_rwlock_t	*rwlock);
pthread_rwlock_trywrlock 函数

非阻塞以写方式请求读写锁(非阻塞请求写锁)

int	pthread_rwlock_trywrlock(pthread_rwlock_t		*rwlock);
同时有多个线程对同一全局数据读、写操作
#include<stdio.h>                                                            
#include<unistd.h>
#include<pthread.h>int counter;    //全局资源pthread_rwlock_t rwlock;void *th_write(void *arg) //写线程
{int t;int i = (int)arg;while(1){t = counter;usleep(1000);pthread_rwlock_wrlock(&rwlock);printf("========write %d: %lu: counter=%d ++counter=%d\n",i,pthread_self(),t,++counter);pthread_rwlock_unlock(&rwlock);usleep(5000);}   return NULL;
}void *th_read(void *arg)
{int i = (int)arg;while(1){pthread_rwlock_rdlock(&rwlock);printf("---------read %d: %lu: %d\n",i,pthread_self(),counter);pthread_rwlock_unlock(&rwlock);usleep(900);}   return NULL;
}
int main(void)
{int i;pthread_t tid[8];pthread_rwlock_init(&rwlock,NULL);for(i = 0; i < 3; i++)pthread_create(&tid[i],NULL,th_write,(void *)i);for(i = 0; i < 5; i++)pthread_create(&tid[i+3],NULL,th_read,(void *)i);//三个写线程,5个读线程for(i = 0; i < 8; i++)pthread_join(tid[i],NULL);pthread_rwlock_destroy(&rwlock);   //释放读写锁return 0;
}        

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383220.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux系统编程---17(条件变量及其函数,生产者消费者条件变量模型,生产者与消费者模型(线程安全队列),条件变量优点,信号量及其主要函数,信号量与条件变量的区别,)

条件变量 条件变量本身不是锁&#xff01;但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。 主要应用函数&#xff1a; pthread_cond_init 函数pthread_cond_destroy 函数pthread_cond_wait 函数pthread_cond_timedwait 函数pthread_cond_signa…

Linux系统编程---18(线程池相关概念及其实现)

线程池 概念&#xff1a; 一堆线程任务队列 作用 避免大量线程频繁的创建/销毁时间成本避免瞬间大量线程创建耗尽资源&#xff0c;程序崩溃危险 实现 创建固定数量的线程创建一个线程安全的任务队列 一种线程使用模式。 线程过多会带来调度开销&#xff0c;进而影响缓…

设计模式--1(设计模式基础,设计模式基本原则,设计模式分类)

设计模式基础 模式 在一定环境中解决某一问题的方案&#xff0c;包括三个基本元素–问题&#xff0c;解决方案和环境。大白话&#xff1a;在一定环境下&#xff0c;用固定套路解决问题。 设计模式 是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使…

设计模式----2(简单工厂模式的概念,简单工厂模式的实现,简单工厂模式的优缺点)

简单工厂模式 简单工厂模式的概念 简单工厂模式属于类的创建型模式,又叫做静态工厂方法模式。通过专门定义一个类来负 责创建其他类的实例&#xff0c;被创建的实例通常都具有共同的父类。 具体分类 工厂&#xff08;Creator&#xff09;角色 简单工厂模式的核心&#xff0…

设计模式---3(工厂方法模式的概念,工厂方法模式的实现,工厂方法模式和简单工厂模式比较)

工厂方法模式 概念 工厂方法模式同样属于类的创建型模式又被称为多态工厂模式 。 工厂方法模式的意义 定义一个创建产品对象的工厂接口&#xff0c;将实际创建工作推迟到子类当中。 核心工厂类不再负责产品的创建&#xff0c;这样核心类成为一个抽象工厂角色&#xff0c;仅…

设计模式---4(抽象工厂模式的概念,产品组和产品等级的概念,抽象工厂模式的实现)

抽象工厂模式 抽象工厂模式的概念 抽象工厂模式是所有形态的工厂模式中最为抽象和最其一般性的。抽象工厂模式可以向 客户端提供一个接口&#xff0c;使得客户端在不必指定产品的具体类型的情况下&#xff0c;能够创建多个产品 族的产品对象。 抽象工厂的角色及其职责 抽象工…

1.c++中初始化列表和构造函数初始化的区别是什么?2.类的成员变量的初始化顺序是按照声明顺序吗?

初始化列表和构造函数初始化的区别是什么&#xff1f; 初始化和赋值对内置类型的成员没有太大的区别&#xff0c;在成员初始化列表和构造函数体内进行&#xff0c;在性能和结果上都是一样的。只有一些需要注意的事项 初始化列表一般情况如下&#xff1a; Date(int year, int …

设计模式---5(建造者模式的概念及其实现,建造者模式的角色与职责,建造者模式和工厂模式的区别)

建造者模式 建造者模式的概念 Builder 模式也叫建造者模式或者生成器模式&#xff0c;是由 GoF 提出的 23 种设计模式中的一种。 Builder 模式是一种对象创建型模式之一&#xff0c;用来隐藏复合对象的创建过程&#xff0c;它把复合对象的 创建过程加以抽象&#xff0c;通过子…

system阻塞SIGCHLD信号原因

system阻塞SIGCHLD信号原因 标签&#xff1a; c 2014-11-08 11:58 198人阅读 评论(0) 收藏 举报 分类&#xff1a; linux编程&#xff08;1&#xff09; 代码1&#xff1a;APUE10.18节的system函数源代码 int system(const char *cmdstring) /* with appropriate signal ha…

设计模式6---(单例模式的概念及其实现(懒汉式和饿汉式),线程安全)

单例模式 单例模式的概念 单例模式是一种对象创建型模式&#xff0c;使用单例模式&#xff0c;可以保证为一个类只生成唯一的实例对象。也就是说&#xff0c;在整个程序空间中&#xff0c;该类只存在一个实例对象。 GoF 对单例模式的定义是&#xff1a;保证一个类、只有一个实…

套接字编程---2(TCP套接字编程的流程,TCP套接字编程中的接口函数,TCP套接字的实现,TCP套接字出现的问题,TCP套接字多进程版本,TCP套接字多线程版本)

TCP模型创建流程图 TCP套接字编程中的接口 socket 函数 #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int socket(int domain, int type, int protocol); domain: AF_INET 这是大多数用来产生socket的协议&#xff0c;使用TCP或UDP来传输&…

Linux中netstat工具详解

简介 Netstat 命令用于显示各种网络相关信息&#xff0c;如网络连接&#xff0c;路由表&#xff0c;接口状态 (Interface Statistics)&#xff0c;masquerade 连接&#xff0c;多播成员 (Multicast Memberships) 等等。 常见参数 -a (all)显示所有选项&#xff0c;默认不显示…

网络基础 2-1(应用层,HTTP三点注意,HTTP协议格式, 最简单的HTTP服务器)

应用层 应用层 负责应用程序之间的数据沟通-----协议都是用户自己定的 自定制协议&#xff1a; 结构化数据传输 序列化&#xff1a; 将数据对象以指定的协议&#xff08;数据格式&#xff09;进行可用于持久化存储或者数据传输时的数据组织 例如在分布式的系统中&#xf…

网络基础2-2(传输层,端口,详谈UDP)

传输层 负责数据能够从发送端传输接收端. 端口号 端口号(Port)标识了一个主机上进行通信的不同的应用程序;在TCP/IP协议中, 用 “源IP”, “源端口号”, “目的IP”, “目的端口号”, “协议号” 这样一个五元组来标识一个通信(可以通过 netstat -n查看);一个端口只能被一个…

网络基础2-3(TCP协议,三次握手,四次挥手,TIME_WAIT状态的作用,TCP如何保证可靠传输,TCP连接中状态转化,滑动窗口,流量控制,快速重传,拥塞窗口,延迟应答,捎带应答,粘包问题)

TCP协议 TCP协议概念 TCP全称为 “传输控制协议(Transmission Control Protocol”). 人如其名, 要对数据的传输进行一个详细的控制 TCP协议格式 1. 源/目的端口号: 表示数据是从哪个进程来, 到哪个进程去; 2. 32位序号/32位确认号: 后面详细讲; 3. 4位TCP报头长度: 表示该…

字符串题目 1 --------判断两个字符串是否为旋转词

题目描述 如果一个字符串为str&#xff0c;把字符串的前面任意部分挪到后面形成的字符串交str的旋转词。比如str“12345”&#xff0c;str的旋转串有“12345”、“45123”等等。给定两个字符串&#xff0c;判断是否为旋转词。 输入描述: 输出包含三行&#xff0c;第一个两个…

字符串题目---2判断两个字符串是否为变形词

题目描述 给定两个字符串str1和str2&#xff0c;如果str1和str2中出现的字符种类出现的一样且每种字符出现的次数也一样&#xff0c;那么str1和str2互为变形词。请判断str1和str2是否为变形词 输入描述: 输入包括3行&#xff0c;第一行包含两个整数n&#xff0c;m(1 \leq n,…

设计模式7----代理模式

代理模式 概念 Proxy 模式又叫做代理模式&#xff0c;是结构型的设计模式之一&#xff0c;它可以为其他对象提供一 种代理&#xff08;Proxy&#xff09;以控制对这个对象的访问。 所谓代理&#xff0c;是指具有与代理元&#xff08;被代理的对象&#xff09;具有相同的接口的…

网络基础3-1(细谈IP协议头, 网络层,子网划分,路由选择,数据链路层,以太网帧格式,MAC地址,再谈ARP协议)

IP协议 IP协议头格式 4位版本号(version): 指定IP协议的版本, 对于IPv4来说, 就是44位头部长度(header length): IP头部的长度是多少个。32bit, 也就是 length * 4 的字节数. 4bit表示大 的数字是15, 因此IP头部大长度是60字节8位服务类型(Type Of Service): 3位优先权字段(已…

网络中典型协议--(DNS,输入url后, 发生的事情. ,ICMP,NAT)

DNS&#xff08;Domain Name System&#xff09; DNS是一整套从域名映射到IP的系统 域名服务器发展背景 TCP/IP中使用IP地址和端口号来确定网络上的一台主机的一个程序. 但是IP地址不方便记忆. 于是人们发明了一种叫主机名的东西, 是一个字符串, 并且使用hosts文件来描述主机…