线程同步-信号量-互斥量-条件变量

news2024/11/16 5:46:49

文章目录

    • 线程同步
      • 信号量
      • 互斥量
      • 条件变量

线程同步

  • 线程同步其实实现的是线程排队。
  • 防止线程同步访问共享资源造成冲突。
  • 多个线程访问共享资源的代码有可能是同一份代码,也有可能是不同的代码;无论是否执行同一份代码,只要这些线程的代码访问同一份可变的共享资源,这些线程之间就需要同步。

1. 问题

  • 同一个进程内的各个线程,共享该进程内的全局变量
  • 如果多个线程同时对某个全局变量进行访问时,有可能达不到预期效果。

2. 信号量和互斥量的选择。

  • 互斥量:为协调共同对一个共享资源的单独访问而设计的;因为进入内核模式,所以性能比临界区差;跨进程,可用于防止程序重复打开运行。
  • 信号量:为控制一个具有有限数量用户资源而设计,互斥锁可以理解为1个用户资源的信号量。
    • 使用时,选择更符合语义的手段:
      • 如果要求最多只允许一个线程进入临界区,则使用互斥量
      • 如果要求多个线程之间的执行顺序满足某个约束,则使用信号量

条件变量:条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。

信号量

1)什么是信号量

  • 此时所指的“信号量”是指用于同一个进程内多个线程之间的信号量。即POSIX信号量,而不是System V信号量(用于进程之间的同步)

  • 用于线程的信号量的原理,与用于进程之间的信号量的原理相同。都有P操作、V操作。

  • 信号量的表示:sem_t 类型

    2) 信号量的初始化

      原型:int  sem_init  (sem_t  *sem,int  pshared,  unsigned int value);
      功能:对信号量进行初始化
      参数:sem,  指向被初始化的信号量
           pshared,  0:表示该信号量是该进程内使用的“局部信号量”, 不再被其它进程共享。
           		   非0:该信号量可被其他进程共享,Linux不支持这种信号量
           		   
           value,  信号量的初值。>= 0
      返回值:成功,返回0   失败, 返回错误码
    

    3) 信号量的P操作

      原型:int   sem_wait (sem_t  *sem);
      返回值:成功,返回0  失败, 返回错误码
    

    4) 信号量的V操作

      原型:int sem_post (sem_t  *sem);
      返回值:成功,返回0  失败, 返回错误码
    

    5) 信号量的删除

     原型:int sem_destroy (sem_t  *sem);
     返回值:成功,返回0   失败, 返回错误码
    

    6) 实例
    主线程循环输入字符串,把字符串存放到一个全局缓存中。新线程从全局缓存中读取字符串,统计该字符串的长度。直到用户输入end

main.c

预期结果:主线程每接收终端输入的一个字符串,子线程就打印字符串和输出字符串长度

  • 信号量被初始化为0,主线程接受一个字符串的时候,执行V操作(信号量+1),此时信号量大于1,子线程执行P操作(-1),然后对字符串做出相应的操作
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#define BUFF_SIZE 80

// 全局变量可以让多个线程访问
char buff[BUFF_SIZE];
sem_t sem;

static void* str_thread_handle(void *arg) 
{
	while(1) {
		//P(sem) -1
		if (sem_wait(&sem) != 0) {
			printf("sem_wait failed!\n");
			exit(1);
		}
		
		printf("string is: %slen=%u\n", buff, (unsigned int)strlen(buff));
		if (strncmp(buff, "end", 3) == 0) {
			break;
		}
	}
}

int main(void)
{
	int ret;
	pthread_t  str_thread;
	void *thread_return;

	// 参数:被初始化信号量 0 给信号量的赋值
	ret = sem_init(&sem, 0, 0);
	if (ret != 0) {
		printf("sem_init failed!\n");
		exit(1);
	}

	// 创建线程
	ret = pthread_create(&str_thread, 0, str_thread_handle, 0);
	if (ret != 0) {
		printf("pthread_create failed!\n");
		exit(1);
	}

	while (1) {
	    // 从终端获取一行输入
		fgets(buff, sizeof(buff), stdin);

		//V(sem) +1
		// 0->1 那么线程就可以执行P操作 1->0
		if (sem_post(&sem) != 0) {
			printf("sem_post failed!\n");
			exit(1);
		}
		
		if (strncmp(buff, "end", 3) == 0) {
			break;
		}
	}

	ret = pthread_join(str_thread, &thread_return);
	if (ret != 0) {
		printf("pthread_join failed!\n");
		exit(1);
	}

	ret = sem_destroy(&sem);
	if (ret != 0) {
		printf("sem_destroy failed!\n");
		exit(1);
	}

	return 0;
}

在这里插入图片描述
练习

    创建2个线程(共有主线程、线程1、线程2共3个线程)
    主线程阻塞式等待用户输入字符串
    主线程每接收到一个字符串之后, 线程1就马上对该字符串进行处理。
    线程1的处理逻辑为:统计该字符串的个数,并记录当时的时间。
    线程1把该字符串处理完后,线程2马上就把处理结果写入文件result.txt
    直到用户输入exit.
    multi_pthread.c

互斥量

1)什么是互斥量
     效果上等同于初值为1的信号量
     互斥量的使用:类型为 pthread_mutex_t
     
2)互斥量的初始化
     原型:int  pthread_mutex_init(pthread_mutex_t *mutex,
                                   pthread_mutexattr_t *attr);
     参数:mutex, 指向被初始化的互斥量
             attr,  指向互斥量的属性
                    一般取默认属性(当一个线程已获取互斥量后,该线程再次获取该信号量,将导致死锁!)

3) 互斥量的获取
    原型:int  pthread_mutex_lock (pthread_mutex_t *mutex);   

4)互斥量的释放
     原型:int  pthread_mutex_unlock (pthread_mutex_t  *mutex);         
     
5)互斥量的删除
     int  pthread_mutex_destroy (pthread_mutex_t *mutex);  
  1. 实例
    main3.c

预期实现效果:主线程和子线程依次把全局变量 -1 ,依次往终端输出结果

  • 当没有用互斥量的时候,同时运行主线程和子线程,对全局变量进行 - 1
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#define BUFF_SIZE 80

// 全局
int global_value = 1000;
pthread_mutex_t  lock;

static void* str_thread_handle(void *arg) 
{
	int i = 0;

	for (i=0; i<10; i++) {
		//pthread_mutex_lock(&lock);

		if (global_value  > 0) {
			// work
			sleep(1);
			printf("soled ticket(%d) to ChildStation(%d)\n",
				global_value, i+1);
		}
		global_value--;
		
		//pthread_mutex_unlock(&lock);
		sleep(1);
	}
}

int main(void)
{
	int ret;
	pthread_t  str_thread;
	void *thread_return;
	int i;

	

	ret = pthread_mutex_init(&lock, 0);
	if (ret != 0) {
		printf("pthread_mutex_init failed!\n");
		exit(1);
	}

	ret = pthread_create(&str_thread, 0, str_thread_handle, 0);
	if (ret != 0) {
		printf("pthread_create failed!\n");
		exit(1);
	}

	for (i=0; i<10; i++) {
		//pthread_mutex_lock(&lock);
		
		if (global_value  > 0) {
			// work
			sleep(1);
			printf("soled ticket(%d) to MainStation(%d)\n",
				global_value, i+1);
		}
		global_value--;
		
		
		//pthread_mutex_unlock(&lock);
		sleep(1);
	}

	ret = pthread_join(str_thread, &thread_return);
	if (ret != 0) {
		printf("pthread_join failed!\n");
		exit(1);
	}

	ret = pthread_mutex_destroy(&lock);
	if (ret != 0) {
		printf("pthread_mutex_destroy failed!\n");
		exit(1);
	}

	return 0;
}
  • 主线程 -10 , 子线程 -10 最后应该是 981 所以不符合预期
  • 原因 主线程和子线程同时对全局变量进行了 -1
    在这里插入图片描述
    使用信号量后
  • 上了两把锁
  • 给主线程和子线程对全局变量进行操作的部分分别上锁
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#define BUFF_SIZE 80

// 全局
int global_value = 1000;
pthread_mutex_t  lock;

static void* str_thread_handle(void *arg) 
{
	int i = 0;

	for (i=0; i<10; i++) {

        /*****上锁*****/
		pthread_mutex_lock(&lock);

		if (global_value  > 0) {
			// work
			sleep(1);
			printf("soled ticket(%d) to ChildStation(%d)\n",
				global_value, i+1);
		}
		global_value--;
		
        /*****开锁*****/
		pthread_mutex_unlock(&lock);
		sleep(1);
	}
}

int main(void)
{
	int ret;
	pthread_t  str_thread;
	void *thread_return;
	int i;

	

	ret = pthread_mutex_init(&lock, 0);
	if (ret != 0) {
		printf("pthread_mutex_init failed!\n");
		exit(1);
	}

	ret = pthread_create(&str_thread, 0, str_thread_handle, 0);
	if (ret != 0) {
		printf("pthread_create failed!\n");
		exit(1);
	}

	for (i=0; i<10; i++) {

        /*****上锁*****/
		pthread_mutex_lock(&lock);
		
		if (global_value  > 0) {
			// work
			sleep(1);
			printf("soled ticket(%d) to MainStation(%d)\n",
				global_value, i+1);
		}
		global_value--;
		
		/*****开锁*****/
		pthread_mutex_unlock(&lock);
		sleep(1);
	}

	ret = pthread_join(str_thread, &thread_return);
	if (ret != 0) {
		printf("pthread_join failed!\n");
		exit(1);
	}

	ret = pthread_mutex_destroy(&lock);
	if (ret != 0) {
		printf("pthread_mutex_destroy failed!\n");
		exit(1);
	}

	return 0;
}
  • 结果分析:相比于上次,每一次输出全局变量就 -1 最后结果为 981
    在这里插入图片描述

条件变量

1.什么是线程条件变量

与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。

条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。

2. 条件变量初始化

  原型:int pthread_cond_init (pthread_cond_t *cond, const pthread_condattr_t *attr);
    参数:cond: 条件变量指针
         attr:条件变量高级属性

3. 唤醒一个等待线程

原型: int pthread_cond_signal (pthread_cond_t *cond);
参数:cond:条件变量指针

4.唤醒所有等待该条件变量的线程

原型: int pthread_cond_broadcast (pthread_cond_t *cond);
参数:cond,  条件变量指针

5.等待条件变量/超时被唤醒

原型: int pthread_cond_timedwait (pthread_cond_t *cond, pthread_mutex_t *mutex, 
									const struct timespec *abstime);
参数:cond,  条件变量指针
      pthread_mutex_t *mutex 互斥量
      const struct timespec *abstime 等待被唤醒的绝对超时时间

6.等待条件变量被唤醒(一般使用这个)

原型: int pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex);
参数:cond,  条件变量指针
      pthread_mutex_t *mutex 互斥量

常见错误码: [EINVAL] cond或mutex无效,
[EINVAL] 同时等待不同的互斥量
[EINVAL] 主调线程没有占有互斥量

7. 释放/销毁条件变量

pthread_cond_destroy  待销毁的条件变量 
原型: int pthread_cond_destroy (pthread_cond_t *cond);
参数:cond,  条件变量指针

main.c

效果:触发信号,子线程向终端打印数据

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

// 注意这里一定要定义为全局变量
pthread_mutex_t mutex;
pthread_cond_t cond;

void *thread1(void *arg)
{

	while (1) {

		printf("thread1 is running\n");
		
		// 加锁
		pthread_mutex_lock(&mutex);
		printf("thread1 lock..\n");

		// 解锁-阻塞等待信号-信号来了-加锁-执行任务
		pthread_cond_wait(&cond, &mutex);


		// 执行任务
		printf("thread1 applied the condition\n");


		// 解锁
		printf("thread1 unlock..\n");
		pthread_mutex_unlock(&mutex);

		sleep(4);

	}
}


void *thread2(void *arg)
{

	while (1) {

		printf("thread2 is running\n");

		pthread_mutex_lock(&mutex);
		printf("thread2 lock..\n");

		pthread_cond_wait(&cond, &mutex);

		printf("thread2 applied the condition\n");

		printf("thread2 unlock..\n");
		pthread_mutex_unlock(&mutex);

		sleep(2);

	}

}

int main()
{

	pthread_t thid1, thid2;

	printf("condition variable study!\n");

	// 初始化互斥锁  效果上等同于初值为1的信号量
	// 初始化条件变量
	pthread_mutex_init(&mutex, NULL);
	pthread_cond_init(&cond, NULL);

	// 创建两个线程
	pthread_create(&thid1, NULL, (void *)thread1, NULL);
	pthread_create(&thid2, NULL, (void *)thread2, NULL);

	// 不断发送信号,唤醒一个线程
	do {
		sleep(10);
		pthread_cond_signal(&cond);

	} while (1);


	return 0;

}

结果分析:为什么两个线程可以同时上锁?
因为 pthread_cond_wait(&cond, &mutex); 执行过程中有一个解锁的过程,所以是解锁后,另一个线程拿到锁。但是如果此线程被信号触发,那么也会立即上锁,执行任务。

在这里插入图片描述

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

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

相关文章

Java避免死锁的几个常见方法(有测试代码和分析过程)

目录 Java避免死锁的几个常见方法 死锁产生的条件 上死锁代码 然后 &#xff1a;jstack 14320 >> jstack.text Java避免死锁的几个常见方法 Java避免死锁的几个常见方法 避免一个线程同时获取多个锁。避免一个线程在锁内同时占用多个资源&#xff0c;尽量保证每个锁…

Geoserver启动时提示:The GEOSERVER_HOME variable is not defined

场景 GeoServer简介、下载、配置启动、发布shapefile全流程(图文实践)&#xff1a; GeoServer简介、下载、配置启动、发布shapefile全流程(图文实践)_霸道流氓气质的博客-CSDN博客 在下载解压之后点击启动bat时提示: The GEOSERVER_HOME environment variable is not defin…

row_number 和 cte 使用实例:背包问题

row_number 和 cte 使用实例&#xff1a;背包问题背包问题01背包解决同一行数据需要引用两次的问题对 for xml 的结果进行引用时的处理完全背包多重背包小结背包问题 最近老顾从新把算法捡了起来&#xff0c;碰到了各种各样以前没见过的&#xff0c;工作中没遇到的问题&#x…

leetcode:快乐数(详解)

前言&#xff1a;内容包括&#xff1a;题目&#xff0c;代码实现&#xff0c;大致思路&#xff0c;代码解读 题目&#xff1a; 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字…

坚鹏:《银行业数字化转型指导意见》政策解读及银行数字化转型

中国银保监会《关于银行业保险业数字化转型的指导意见》政策解读及银行数字化转型课程背景&#xff1a; 很多银行存在以下问题&#xff1a; 不知道如何准确理解中国银保监会《关于银行业保险业数字化转型的指导意见》相关政策 不清楚中国银保监会《关于银行业保险业数字化转型…

使用AI进行“文本纠错”

AI在现实中的应用有很多&#xff0c;你有没有想过&#xff0c;它还可以进行文本纠错呢&#xff1f;传统的校对既耗时又枯燥&#xff0c;通过“AI纠错”&#xff0c;不仅能更快完成&#xff0c;还能提高准确度。那么AI“文本纠错”背后的原理是什么呢&#xff1f;和我一起看看吧…

Python综合案例-小费数据集的数据分析(详细思路+源码解析)

目录 1. 请导入相应模块并获取数据。导入待处理数据tips.xls&#xff0c;并显示前5行。 2、分析数据 3.增加一列“人均消费” 4查询抽烟男性中人均消费大于5的数据 5.分析小费金额和消费总额的关系&#xff0c;小费金额与消费总额是否存在正相关关系。画图观察。 6分析男女顾…

chatGPT写文章攻略-用chatGPT写网文

chatGPT可以写中文吗 ChatGPT可以写中文。在过去的几年中&#xff0c;许多深度学习机器翻译模型已经开始探索中英文翻译、去噪声、语音识别等任务&#xff0c;并且在这些任务中ChatGPT具有最先进的表现。 目前&#xff0c;例如GPT-3和GPT-2都可以用来生成中文文本。为此&…

以太坊上海升级,DeFi 3.0的序章

引言 距离以太坊Shapella升级&#xff08;也曾被称为上海升级&#xff09;仅剩一天的时间&#xff01;自2015年以太坊上线以来&#xff0c;它已成为世界排名第二的加密虚拟资产&#xff0c;以太坊诞生于行业的意义在于它能够让开发者构建智能合约和去中心化应用&#xff08;DAp…

Jumpserver与Freeipa集成(以及其他配置)

背景&#xff1a; jumpserver的安装参照&#xff1a;jumpserver的简单安装使用&#xff0c;Freeipa的安装参照&#xff1a;Freeipa的简单搭建配置。准备将Freeipa与Jumpserver集成。其实Freeipa搭建后linux客户端如果安装了Freeipa client。也能完成用户的授权权限管理了&…

肖 sir_就业课__005项目数据

项目数据 一、项目周期 &#xff08;1&#xff09;新项目&#xff1a;从无到有&#xff0c;从项目的开始到上线的时间 时间长&#xff1a;3个月、6个月、1年、2年 &#xff08;2&#xff09;老项目&#xff1a;迭代项目 迭代周期&#xff1a;1个月、2个月、3个月迭代、 &#…

现在是香港推动Web3的“正确时机” 将采取监管与发展并重策略

香港财政司司长陈茂波在香港特区政府网站发表司长随笔《发展Web3—守正创新 稳慎前行》&#xff0c;提出为了让Web3稳慎走好创新发展的道路&#xff0c;政府将采取“适当监管”和“推动发展”两者并重的策略&#xff0c;确保虚拟资产行业可持续和负责任发展。 此前&#xff0c;…

java_集合

1.集合 集合分为单列集合&#xff08;collection&#xff09;和双列集合&#xff08;map&#xff09;. 单列就是每个位置只有一个值&#xff0c;双列则是每个位置都是一对键值对&#xff0c;类似于python的字典。 2.collection 其中&#xff0c;collection又可以分为List…

全国大学生智能汽车竞赛——安装Ubuntu操作系统(双系统)

1.1 电脑分区 1.1.1 分区原因 由于我们想要在电脑上同时安装Windows和Ubuntu系统&#xff0c;所以就要在window使用的内存中划分出来一段用来给Ubuntu系统使用&#xff0c;相当于一个应用程序一样 1.1.2 分区步骤 1.右击此电脑&#xff0c;点击管理&#xff0c;然后双击左侧…

【刷题笔记】--dp--376. 摆动序列122. 买卖股票的最佳时机 II

感觉自己dp还不是很会&#xff08;/(ㄒoㄒ)/~~ 写dp题的步骤&#xff1a;①通过定义子问题&#xff0c;确定dp[ ] or dp[ ][ ] 表示的含义 ②写出子问题的递归关系 ③确定初始条件 题目&#xff1a; 思路&#xff1a; ①确定dp的含义&#xff1a;dp[i]表示 到i位置&#x…

网络信息安全(三层设备部署DHCP服务器与DHCP中继、ICMP协议)

文章目录三层路由器部署DHCP服务器配置trunkVLAN创建将端口加入对应vlan路由创建子接口路由器创建地址池服务器配置静态IPDHCP服务器部署创建作用域在三层设备配置DHCP中继DHCP中继原理ICMP协议概述用途封装格式三层路由器部署DHCP服务器 配置trunk VLAN创建 这里以S1为例&…

Vulnhub:Digitalworld.local (Development)靶机

kali&#xff1a;192.168.111.111 靶机&#xff1a;192.168.111.130 信息收集 端口扫描 nmap -A -v -sV -T5 -p- --scripthttp-enum 192.168.111.130 查看网站首页源码 访问development目录&#xff0c;提示存在一个流量包 查看流量包发现另一个网站路径&#xff1a;/devel…

多模态之论文笔记ViLT

文章目录ViLT: Vision-and-Language Transformer Without Convolution or Region Supervision一. 简介1.1 摘要1.2 文本编码器&#xff0c;图像编码器&#xff0c;特征交互复杂度分析1.2 特征交互方式分析1.3 图像特征提取分析二. 方法 Vision-and-Language Transformer2.1. 方…

【高危】Apache Linkis JDBC EngineConn 插件<1.3.2 存在反序列化漏洞

漏洞描述 Apache Linkis 是一个用于将上层应用与底层数据引擎解耦&#xff0c;提供标准化接口的中间件。 该项目受影响版本存在反序列化漏洞&#xff0c;由于ConnectionManager.java中未对dbUrl、username、password等参数进行充分过滤&#xff0c;当恶意用户完全控制应用程序…

缘起|蚂蚁应用级服务发现的实践之路

文&#xff5c;肖健&#xff08;花名&#xff1a;昱恒&#xff09;蚂蚁集团技术专家、SOFARegistry Maintainer专注于服务发现领域&#xff0c;目前主要从事蚂蚁注册中心 SOFARegistry 的设计和研发工作。本文 8339 字 阅读 15 分钟PART. 1前言什么是服务发现&#xff1f;我们今…