Linux——线程同步(互斥锁、信号量、读写锁、自旋锁、条件变量)

2023-11-14

前言

当多个控制线程共享相同的内存时,需要确保每个线程看到一致的数据视图,若每个线程使用的变量都是其他线程不会读取或修改的,那么就不存在一致性概念,同样地,若变量是只读的,多个线程同时读取该变量也不会有一致性问题,但是当某个线程可以修改变量,而其他线程也可以读取或者修改这个变量的时候,就需要对线程进行同步,以确保它们在访问变量的存储内容时不会访问到无效的数值。

正如上一篇博客中写到的那样,对于这个变量的自增,多个线程同时访问一块内存空间时,某个线程还未完成它的操作,另外一个线程也进行了这个操作,导致结果与我们的与其有出入。
今天的博客我们就来解决这个问题:

线程同步的概念

线程同步指的是当一个线程在对某个临界资源进行操作时,其他线程都不可以对这个资源进行操作,直到该线程完成操作,其他线程才能操作,也就是协同步调,让线程按预定的先后次序进行运行,线程同步的方法有四种:

  • 互斥锁
  • 信号量
  • 条件变量
  • 读写锁

互斥锁

概念
互斥锁只有两种状态,加锁状态和解锁状态,
如果一个线程对已经处于加锁状态的互斥锁进行加锁操作,则加锁操作会阻塞,直到正在对互斥锁加锁状态线程进行解锁操作。
互斥锁的接口

#include<pthread.h>

pthread_mutex_t  //互斥锁的类型

初始化互斥锁

int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr);

第二个参数为锁属性,一般传入NULL
加锁操作

int pthread_mutex_lock(pthread_mutex_t *mutex)

尝试加锁,不会阻塞

int pthread_mutex_trylock(pthread_mutex_t *mutex)

解锁操作

int pthread_mutex_unlock(pthread_mutex_t *mutex)

销毁互斥锁

int pthread_mutex_destroy(pthread_mutex_t *mutex)

解决之前的问题:
在这里插入图片描述
多次测试后发现,wg的值一直都是5000,说明互斥锁发挥了作用。但是其实反复的加锁解锁操作在一定程度上也影响了程序的执行效率,但这也是必须的牺牲,为了程序的正常执行。

使用
示例:模拟两个线程竞争一个打印机,A线程使用打印机输出一个a,使用完成后输出一个a,B线程也一样,这样我们的输出结果不会出现abab/baba
如何让两个线程操作的是同一把互斥锁:将锁定义到全局

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<time.h>

#include<pthread.h>

pthread_mutex_t mutex;

void *threadFun(void *arg) //B线程
{
	int i = 0;
	for(;i < 5;i++)
	{
		pthread_mutex_lock(&mutex);
		printf("B");
		fflush(stdout);
		int n = rand() % 3;
		sleep(n);

		printf("B");
		fflush(stdout);
		pthread_mutex_unlock(&mutex);

		n = rand() % 3;
		sleep(n);
	}	
}

void threadMain() //A线程
{
	int i = 0;
	for(;i < 5;i++)
	{
		pthread_mutex_lock(&mutex);
		printf("A");
		fflush(stdout);
		int n = rand() % 3;
		sleep(n);

		printf("A");
		fflush(stdout);
		pthread_mutex_unlock(&mutex);

		n = rand() % 3;
		sleep(n);
	} 
}

int main()
{
	srand((unsigned int)time(NULL));

	pthread_mutex_init(&mutex,NULL);

	pthread_t id;
	int res = pthread_create(&id,NULL,threadFun,NULL);
	assert(res == 0);

	threadMain();

	pthread_join(id,NULL);
	pthread_mutex_destroy(&mutex);
	exit(0);
}

执行结果
在这里插入图片描述

信号量

概念
线程级信号量和进程级信号量的原理是相同的,信号量也是特殊的计数器,当值>0时,记录的是临界资源的个数,=0时,表示没有临界资源可用,这时对信号量执行P操作,则线程会被阻塞。
信号量的接口

#include<semaphore.h>

sem_t //线程级信号量的类型

初始化信号量并且给定初始值

int sem_init(sem_t *sem,int shared,int val);

对信号量执行P操作

int sem_wait(sem_t *sem);

对信号量执行V操作

int sem_post(sem_t *sem);

销毁信号量

int sem_destroy(sem_t *sem);

信号量和互斥锁的区别

  • 互斥锁只有两种状态,信号量的值可以大于1
  • 在一个线程中对互斥锁的加锁和解锁必须成对出现,信号量可以在线程间混用

读写锁

概念
读写锁在互斥锁的基础上,允许一个更高的并行性,读写锁一共有三种状态:

  • 解锁状态
    任何线程任何方式加锁都可以成功。
  • 读加锁状态
    一个线程对锁执行读加锁,则可以成功,但是一个线程如果执行写加锁,则会被阻塞,直到所有读加锁的线程都执行了解锁操作。
  • 写加锁状态
    一个线程只要对锁已经写加锁,所有的加锁操作都会被阻塞。

读写锁的接口

#include<pthread.h>

pthread_rwlock_t;  //读写锁的类型

初始化读写锁

int pthread_rwlock_init(pthread_rwlock_t *rwlock, pthread_rwlock_t *attr);

读加锁操作

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)

写加锁操作

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock)

解锁操作

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)

销毁互斥锁

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);   

自旋锁

和互斥锁类似,只是当加锁操作被阻塞,阻塞的方式不同:互斥锁是通过将线程睡眠,自旋锁则是通过忙等待的方式。
自旋锁一般适用的场景是锁被其他线程短期持有(很快会被释放),而且等待该锁的线程不希望在阻塞期间被取消调度,因为这会带来一些开销。

条件变量

概念
条件变量给多个线程提供了一个会和的场所,条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件的发生。
条件本身是由互斥量保护的,线程在改变条件状态前必须锁住互斥量,其他线程在获得互斥量之前不会察觉到这种改变,因为必须锁定互斥量以后才能计算条件。
条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不同,常和互斥锁一起使用,在使用时,条件变量被用来阻塞一个线程,线程往往解开互斥锁并等待条件发生变化,一旦其他的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。
条件变量提供了一种线程间的通知机制:当某个共享数据达到某个值的时候,唤醒等待
这个共享数据的线程。

条件变量的使用

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr);
//第二个参数通常为空,且被忽略
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); 
// 阻塞等待一个条件变量,会阻塞, 传递的是加锁状态的锁 int 
//该函数会阻塞等待条件变量(参数1满足),并释放已掌握的互斥锁,
int pthread_cond_timewait(pthread_cond_t *cond, pthread_mutex_t *mutex,const struct timespec* abstime); 
//限时阻塞等待一个条件变量,在给定时间前条件没有满足返回ETIMEOUT
//abstime以与time系统调用相同意义的绝对时间的形式出现,0表示距离格林尼治时间1970.1.1 0秒
pthread_cond_signal(pthread_cond_t *cond);
 //唤醒单个阻塞在条件变量上的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
 //唤醒所有阻塞在条件变量上的线程
int pthread_cond_destroy(pthread_cond_t *cond);
//只有当没有线程在该条件变量上等待时才能注销这个条件变量,否则返回EBUSY。

代码示例

#include <stdio.h> 
#include <stdlib.h> 
#include <assert.h> 
#include <string.h> 
#include <unistd.h>
 #include <pthread.h>
 
pthread_mutex_t mutex;
pthread_cond_t cond;
 
char buff[128] = {0};

void *fun(void *arg)
{
	int flg = *(int*)arg;
	while(1)
	{
		 pthread_mutex_lock(&mutex);
		 pthread_cond_wait(&cond, &mutex);  //  mutex肯定是一个加锁状态的锁,以互斥方式将当前线程添加到等待条件变量的队列中        
		 pthread_mutex_unlock(&mutex);
		
		if(strncmp(buff,"end",3) == 0)
		{
			break;
		}
		
 		printf("fun%d:%s\n",flag,buff);
 		memset(buff,0,128);
	}

	printf("fun over\n");
}
int main()
{
	 pthread_cond_init(&cond, NULL);
	 pthread_mutex_init(&mutex, NULL);
 
 	int flag1 = 1;
 	int flag2 = 2;
 	pthread_t id1, id2; 
 	pthread_create(&id1, NULL, fun, (void*)(&flag1));
 	pthread_create(&id2, NULL, fun, (void*)(&flag2));
 
 	  while(1)
 	  {       
 	  	 printf("input: ");
 	  	 fflush(stdout);
 	  	 fgets(buff, 127, stdin);
 	  	 
        if(strncmp(buff, "end", 3) == 0)
        {
	        pthread_mutex_lock(&mutex);
	        pthread_cond_broadcast(&cond);
	        pthread_mutex_unlock(&mutex);
	        break;
        }
        else
        {
        	pthread_mutex_lock(&mutex);
	        pthread_cond_signal(&cond);
	        pthread_mutex_unlock(&mutex);
        }
      }
      
     pthread_join(id1, NULL);
     pthread_join(id2, NULL);

	 pthread_mutex_destroy(&mutex);
	 pthread_cond_destroy(&cond);
 
  	 exit(0);
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux——线程同步(互斥锁、信号量、读写锁、自旋锁、条件变量) 的相关文章

随机推荐

  • .net 面试题(高级开发人员篇)

    1 DateTime Parse myString 这行代码有什么问题 有问题 当myString不能满足时间格式要求的时候 会引发异常 建议使用DateTime TryParse 2 PDB是什么东西 在调试中它应该放在哪里 PDB是用于
  • python opencv打开摄像头失败报错,尝试使用网络摄像头python opencv捕获视频时gstreamer严重错误...

    i m trying to take a video with webcam using opencv and python with a simple code import numpy as np import cv2 cap cv2
  • C++字符串常量跨平台编译问题

    C 字符串常量跨平台编译问题 与字符串编码相关 有需要的朋友可以参考下 问题 在C 代码中 给一个string类型的变量赋值一个中文字符串常量 例如 string s 中文字符串 变量s中保存的字节内容是什么 如果源文件的编码格式转换了 比
  • Python实现快乐的数字

    题目要求 编写一个算法来确定一个数字是否 快乐 快乐的数字按照如下方式确定 从一个正整数开始 用其每位数的平方之和取代该数 并重复这个过程 直到最后数字要么收敛等于1且一直等于1 要么将无休止地循环下去且最终不会收敛等于1 能够最终收敛等于
  • 浅谈 Spring Cloud Alibaba

    Spring Cloud Alibaba 一 什么是 Spring Cloud Alibaba 二 它能做什么 三 它的组件包括什么 四 如何使用 一 什么是 Spring Cloud Alibaba Spring Cloud Alibab
  • 【猿人学WEB题目专解】专栏通知

    据说 看我文章时 关注 点赞 收藏 的 帅哥美女们 心情都会不自觉的好起来 前言 作者简介 大家好我是 user from future 意思是 来自未来的用户 寓意着未来的自己一定很棒 个人主页 点我直达 在这里肯定能找到你想要的 专栏介
  • 使用Intellij idea创建一下java后台项目并实现第一个接口

    创建java后台项目 1 下载并安装Intellij idea 2 选择Create New Project 3勾选左侧的Spring Initializr 点击next 4 填写项目名称和包名 点击next 5 勾选左侧web 然后勾选中
  • 怎么用群晖webdav实现外网映射网络驱动器

    前几天刚作好群晖nas局域网内的磁盘映射功能 今天老板又想实现在家里也能跟在公司一样的方便访问映射功能 因为使用网页操作实在太麻烦了 这可怎么办官方提供的 Assistant工具只能操作局域网的 又没有独立的IP地址 只好求助技术支持了 还
  • springboot项目中解决SpringBoot上传图片后访问不到的问题

    1 解决SpringBoot上传图片后访问不到的问题 问题描述 前后端不分离项目 前端 thymeleaf 后端 mybatis springboot架构 在前端上传图片之后 上传到指定的本地路径 路径为 idea项目下的 resource
  • springboot security学习-01

    引入相关依赖
  • 心血来潮,自实现标准库的shared_ptr

    include
  • 虚拟机怎么重启服务器,虚拟机服务器重启命令行

    虚拟机服务器重启命令行 内容精选 换一换 确认服务器服务是否开启 登录虚拟机内部 执行如下命令 查看系统的端口监听状态 如图1所示 netstat ntplWindows虚拟机可以在命令行中执行netstat ano查看系统的端口监听状态
  • awk 以及 sed 的一下常用方法

    前段时间看了慕课网上关于awk 和 sed 的一些用法 以及自己实际中用到的做一个简单的总结记录 方便以后使用到的时候查阅 1 首先是正则表达式 边界字符 1 首字符 2 尾字符 例子 筛选出以a字母后面跟着3个以上数字 a字母开头 b字母
  • 【JS】简易ATM取款机

    页面显示请输入你的操作 不输入4就会反复弹出这个对话框 1 存款 2 取款 3 查看余额 4 退出 while嵌套switch 循环的时候 需要反复提示输入框 所以提示框写到循环里面 退出的条件是用户输入了4 如果是4 则结束循环 不在弹窗
  • 文件名称乱码,content-disposition 获取filename乱码的解决办法

    1 前端先确认是否能在响应头里获取到filename 如果获取不到 需后端服务器设置请求头Access Control Expose Headers 设置headers name 文件名称 中国 xlsx public static Htt
  • 微信小程序接入客服功能开发流程

    首先看官方文档这样说的 1 小程序内 开发者在小程序内添加客服消息按钮组件 用户可在小程序内唤起客服会话页面 给小程序发消息 客服消息使用指南 微信开放文档 客服消息按钮组件 button 微信开放文档 在线客服是通过按钮组件来绑定的 所以
  • mac安装MongoDB以后 报错 command not found: mongod

    mac安装MongoDB以后 报错 command not found mongod 阐述 这里具体就不介绍怎么安装MongoDB了 可以自行的去查教程 官网 MongoDB官网 菜鸟教程 建议使用这个 报错问题 当执行 mongod ve
  • 循环里,设置执行间隔

    在循环内控制 多长时间执行一次循环体 js没有提供这种方法 就只能自己写个判断时间的函数 第一种方法 es6之前我们可以用时间戳来判断 function sleep n var start new Date getTime 定义起始时间的毫
  • AQS同步组件-CountDownLatch解析和案例

    CountDownLatch原理 CountDownLatch是通过一个计数器来实现的 计数器的初始化值为线程的数量 每当一个线程完成了自己的任务后 计数器的值就相应得减1 当计数器到达0时 表示所有的线程都已完成任务 然后在闭锁上等待的线
  • Linux——线程同步(互斥锁、信号量、读写锁、自旋锁、条件变量)

    前言 当多个控制线程共享相同的内存时 需要确保每个线程看到一致的数据视图 若每个线程使用的变量都是其他线程不会读取或修改的 那么就不存在一致性概念 同样地 若变量是只读的 多个线程同时读取该变量也不会有一致性问题 但是当某个线程可以修改变量