Linux-笔记 线程同步机制

目录

前言

实现

信号量(Semaphore)

计数型信号量

二值信号量 

信号量的原语操作

无名信号量的操作函数

例子

互斥锁(mutex)

互斥锁的操作函数

例子

自旋锁 (Spinlock)

自旋锁与互斥锁的区别

自旋锁的操作函数

例子


前言

        线程同步是为了对共享资源的访问进行保护,确保数据的一致性,由于进程中会有多个线程的存在,每个线程对共享资源的并发访问就会出现数据的一致性问题。

实现

        实现线程同步的机制有很多,比较常用的有:信号量、互斥锁、自旋锁、条件变量、读写锁等等,这里仅探讨linux下的线程同步机制。

信号量(Semaphore)

        信号量是一种非常常见的进程同步机制,主要用于多线程或多进程环境中,以控制对共享资源的访问。如果是按照命名区分可以分为有名信号量和无名信号量,本章主要使用无名信号量,因为无名信号量常用于线程同步,它们没有全局名称,只在进程内部有效,使用起来简单且高效。

如果按照信号量值分类可以分成计数型信号量与二值信号量。

计数型信号量
  • 计数信号量的值可以是一个非负整数。
  • 它用于控制多个相同资源的访问数量。
  • 当一个进程(或线程)试图获取资源时,它会检查信号量的值是否大于零。如果大于零,则信号量减一,进程继续执行;否则,进程会进入等待状态,直到信号量的值大于零。
  • 当一个进程释放资源时,它会将信号量的值加一,并唤醒等待队列中的一个进程(如果有的话)。
二值信号量 
  • 二元信号量的值只有0和1两种状态,类似于互斥锁(Mutex)。
  • 当信号量为1时,表示资源是可用的;当信号量为0时,表示资源不可用。
  • 一个进程获取资源时,将信号量设置为0;当进程释放资源时,将信号量设置为1。
  • 二元信号量主要用于确保一次只有一个进程访问某个资源。
信号量的原语操作
  • P操作(Proberen):也叫wait或down操作,表示请求资源。

    • 对于计数信号量,P操作会将信号量的值减一,如果结果为负,进程将进入等待状态。
    • 对于二元信号量,如果信号量值为0,进程将进入等待状态;如果为1,则设置为0,继续执行。
  • V操作(Verhogen):也称为signalup操作,表示释放资源。

    • 对于计数信号量,V操作会将信号量的值加一,如果有进程在等待,将唤醒一个等待的进程。
    • 对于二元信号量,将信号量设置为1,并唤醒一个等待的进程(如果有的话)。
无名信号量的操作函数

1、sem_init:初始化一个无名信号量

int sem_init(sem_t *sem, int pshared, unsigned int value);sem_t sem;
if (sem_init(&sem, 0, 1) != 0) {perror("sem_init");exit(EXIT_FAILURE);
}
  • 参数

    • sem:指向信号量对象的指针。
    • pshared:指定信号量是否在进程间共享。0 表示用于线程间同步,非 0 表示用于进程间同步。
    • value:信号量的初始值。
  • 返回值

    • 成功返回 0,失败返回 -1 并设置 errno

2、sem_destroy:销毁一个无名信号量。

int sem_destroy(sem_t *sem);if (sem_destroy(&sem) != 0) {perror("sem_destroy");exit(EXIT_FAILURE);
}
  • 参数

    • sem:指向要销毁的信号量对象的指针。
  • 返回值

    • 成功返回 0,失败返回 -1 并设置 errno

3、sem_wait:等待信号量,将信号量的值减1。如果信号量的值为0,则阻塞直到信号量的值大于0。

int sem_wait(sem_t *sem);if (sem_wait(&sem) != 0) {perror("sem_wait");exit(EXIT_FAILURE);
}
  • 参数

    • sem:指向信号量对象的指针。
  • 返回值

    • 成功返回 0,失败返回 -1 并设置 errno

4、sem_post:释放信号量,将信号量的值加1。如果有其他线程正在阻塞等待该信号量,则唤醒其中一个线程。

int sem_post(sem_t *sem);if (sem_post(&sem) != 0) {perror("sem_post");exit(EXIT_FAILURE);
}
  • 参数

    • sem:指向信号量对象的指针。
  • 返回值

    • 成功返回 0,失败返回 -1 并设置 errno

5、sem_trywait:尝试等待信号量。如果信号量的值大于0,将信号量的值减1并立即返回。如果信号量的值为0,则立即返回错误而不阻塞。

int sem_trywait(sem_t *sem);if (sem_trywait(&sem) != 0) {if (errno == EAGAIN) {printf("信号量当前不可用\n");} else {perror("sem_trywait");exit(EXIT_FAILURE);}
}
  • 参数

    • sem:指向信号量对象的指针。
  • 返回值

    • 成功返回 0,失败返回 -1 并设置 errno

6、sem_timedwait:在指定的时间内等待信号量。如果在指定时间内信号量的值变为正数,则将其减1并返回。如果超过指定时间,返回错误。

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += 5; // 等待5秒if (sem_timedwait(&sem, &ts) != 0) {if (errno == ETIMEDOUT) {printf("等待超时\n");} else {perror("sem_timedwait");exit(EXIT_FAILURE);}
}
  • 参数

    • sem:指向信号量对象的指针。
    • abs_timeout:指定的绝对超时时间。
  • 返回值

    • 成功返回 0,失败返回 -1 并设置 errno
例子
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>sem_t sem_g, sem_p;  // 定义两个信号量
char ch = 'a';void *pthread_g(void *arg)  // 改变字符 ch 的值
{while (1){sem_wait(&sem_g);  //第一次为0阻塞ch++;sleep(1);sem_post(&sem_p);  //加1}
}void *pthread_p(void *arg)  // 打印字符 ch 的值
{while (1){sem_wait(&sem_p); //非0,减一执行printf("%c", ch);fflush(stdout);sem_post(&sem_g); //为0,加一}
}int main()
{pthread_t tid1, tid2;sem_init(&sem_g, 0, 0);  // 初始化信号量sem_init(&sem_p, 0, 1);  //sem_p先获得1,先执行pthread_create(&tid1, NULL, pthread_g, NULL);pthread_create(&tid2, NULL, pthread_p, NULL);pthread_join(tid1, NULL);pthread_join(tid2, NULL);return 0;
}

互斥锁(mutex)

        互斥锁又叫互斥量,在对共享资源进行访问的时候可以进行上锁,访问结束后可以解锁。当有一个线程对互斥锁进行上锁后,其他线程也想对互斥锁进行上锁就会被阻塞,直到对互斥锁上锁的线程释放互斥锁为止。如果释放互斥锁时有一个以上的线程阻塞,那么这些阻塞的线程会被唤醒,它们都会尝试对互斥锁进行上锁,当有一个线程成功对互斥锁上锁之后,其它线程就不能再次上锁了,只能再次陷入阻塞,等待下一次解锁。

        简单说,互斥量就是为了确保同一时间只有一个线程能够访问一个共享资源。

互斥锁的操作函数

1、pthread_mutex_init:初始化一个互斥锁。

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);pthread_mutex_t mutex;
if (pthread_mutex_init(&mutex, NULL) != 0) {perror("pthread_mutex_init");exit(EXIT_FAILURE);
}
  • 参数

    • mutex:指向互斥锁对象的指针。
    • attr:互斥锁属性。可以是 NULL,表示使用默认属性。
  • 返回值

    • 成功返回 0,失败返回错误代码。

2、pthread_mutex_destroy:销毁一个互斥锁。

int pthread_mutex_destroy(pthread_mutex_t *mutex);if (pthread_mutex_destroy(&mutex) != 0) {perror("pthread_mutex_destroy");exit(EXIT_FAILURE);
}
  • 参数

    • mutex:指向要销毁的互斥锁对象的指针。
  • 返回值

    • 成功返回 0,失败返回错误代码。

3、pthread_mutex_lock:锁定一个互斥锁。如果互斥锁已经被锁定,则阻塞直到互斥锁可用。

int pthread_mutex_lock(pthread_mutex_t *mutex);if (pthread_mutex_lock(&mutex) != 0) {perror("pthread_mutex_lock");exit(EXIT_FAILURE);
}
  • 参数

    • mutex:指向要锁定的互斥锁对象的指针。
  • 返回值

    • 成功返回 0,失败返回错误代码。

4、pthread_mutex_trylock:尝试锁定一个互斥锁。如果互斥锁已经被锁定,则立即返回错误而不阻塞。

int pthread_mutex_trylock(pthread_mutex_t *mutex);if (pthread_mutex_trylock(&mutex) != 0) {if (errno == EBUSY) {printf("互斥锁当前已被锁定\n");} else {perror("pthread_mutex_trylock");exit(EXIT_FAILURE);}
}
  • 参数

    • mutex:指向要锁定的互斥锁对象的指针。
  • 返回值

    • 成功返回 0,失败返回错误代码。

5、pthread_mutex_unlock:解锁一个互斥锁。

int pthread_mutex_unlock(pthread_mutex_t *mutex);if (pthread_mutex_unlock(&mutex) != 0) {perror("pthread_mutex_unlock");exit(EXIT_FAILURE);
}
  • 参数

    • mutex:指向要解锁的互斥锁对象的指针。
  • 返回值

    • 成功返回 0,失败返回错误代码。
例子
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>#define NUM_ITERATIONS 1000000int counter = 0;
pthread_mutex_t mutex;void* increment(void* arg) {for (int i = 0; i < NUM_ITERATIONS; i++) {pthread_mutex_lock(&mutex);   // 加锁counter++;pthread_mutex_unlock(&mutex); // 解锁}return NULL;
}void* decrement(void* arg) {for (int i = 0; i < NUM_ITERATIONS; i++) {pthread_mutex_lock(&mutex);   // 加锁counter--;pthread_mutex_unlock(&mutex); // 解锁}return NULL;
}int main() {pthread_t thread1, thread2;// 初始化互斥锁if (pthread_mutex_init(&mutex, NULL) != 0) {perror("pthread_mutex_init");exit(EXIT_FAILURE);}// 创建线程if (pthread_create(&thread1, NULL, increment, NULL) != 0) {perror("pthread_create");exit(EXIT_FAILURE);}if (pthread_create(&thread2, NULL, decrement, NULL) != 0) {perror("pthread_create");exit(EXIT_FAILURE);}// 等待线程完成if (pthread_join(thread1, NULL) != 0) {perror("pthread_join");exit(EXIT_FAILURE);}if (pthread_join(thread2, NULL) != 0) {perror("pthread_join");exit(EXIT_FAILURE);}// 销毁互斥锁if (pthread_mutex_destroy(&mutex) != 0) {perror("pthread_mutex_destroy");exit(EXIT_FAILURE);}// 输出结果printf("Final counter value: %d\n", counter);return 0;
}

自旋锁 (Spinlock)

        自旋锁(spinlock)是一种用于多线程编程的同步机制,用于保护共享资源,使同一时间只有一个线程可以访问该资源。与互斥锁不同,自旋锁在等待锁时不会使线程休眠,而是不断地检查锁的状态,直到获得锁。这种机制在短时间内需要高频率访问共享资源的情况下比较有效。

        自旋锁的不足之处在于:自旋锁一直占用的 CPU,它在未获得锁的情况下,一直处于运行状态(自旋), 所以占着 CPU,如果不能在很短的时间内获取锁,这无疑会使 CPU 效率降低。 试图对同一自旋锁加锁两次必然会导致死锁,而试图对同一互斥锁加锁两次不一定会导致死锁。

自旋锁与互斥锁的区别
特性自旋锁(Spinlock)互斥锁(Mutex)
等待机制忙等待,循环检查锁状态睡眠等待,线程被挂起
CPU开销高(忙等待消耗CPU)低(等待时线程睡眠)
上下文切换不会发生可能发生
适用场景锁持有时间短,多处理器系统锁持有时间长,各类系统
复杂度简单复杂(需要操作系统调度支持)
死锁检测一般没有自动检测机制可以有自动检测机制
自旋锁的操作函数

1、spin_lock_init:初始化一个自旋锁。

int pthread_spin_init(pthread_spinlock_t *lock, int pshared);static pthread_spinlock_t spin;//定义自旋锁
pthread_spin_init(&spin, PTHREAD_PROCESS_PRIVATE);
  • 参数
    • lock:指向 pthread_spinlock_t 类型的指针,用于存储初始化后的自旋锁。
    • pshared:指定锁的共享性质,可以是 PTHREAD_PROCESS_PRIVATEPTHREAD_PROCESS_SHARED
  • 返回值
    • 成功初始化返回 0,失败返回错误码。

2、pthread_spin_destroy:用于销毁自旋锁。

int pthread_spin_destroy(pthread_spinlock_t *lock);static pthread_spinlock_t spin;
/* 销毁自旋锁 */pthread_spin_destroy(&spin);
  • 参数
    • lock:指向 pthread_spinlock_t 类型的指针,指定要销毁的自旋锁。
  • 返回值
    • 成功销毁返回 0,失败返回错误码。

3、pthread_spin_lock:用于获取自旋锁,如果自旋锁已经被其他线程持有,则当前线程会一直自旋等待直到获取锁为止。

int pthread_spin_lock(pthread_spinlock_t *lock);static pthread_spinlock_t spin;//定义自旋锁
pthread_spin_lock(&spin); //自旋锁上锁
  • 参数
    • lock:指向 pthread_spinlock_t 类型的指针,指定要获取的自旋锁。
  • 返回值
    • 成功获取锁返回 0,失败返回错误码。

4、pthread_spin_trylock:用于尝试获取自旋锁,如果自旋锁已经被其他线程持有,则立即返回失败,不会进入自旋等待。

int pthread_spin_trylock(pthread_spinlock_t *lock);
  • 参数
    • lock:指向 pthread_spinlock_t 类型的指针,指定要尝试获取的自旋锁。
  • 返回值
    • 成功获取锁返回 0,失败返回 EBUSY(锁已经被持有)或其他错误码。

5、pthread_spin_unlock:用于释放自旋锁,允许其他线程继续获取该自旋锁。

int pthread_spin_unlock(pthread_spinlock_t *lock);pthread_spin_unlock(&spin);//自旋锁解锁
  • 参数
    • lock:指向 pthread_spinlock_t 类型的指针,指定要释放的自旋锁。
  • 返回值
    • 成功释放锁返回 0,失败返回错误码。
例子
#include <stdio.h>
#include <pthread.h>
#include <unistd.h> // 提供 sleep 函数pthread_spinlock_t lock;
int shared_variable = 0;void* thread_func(void* arg) {for (int i = 0; i < 5; ++i) {pthread_spin_lock(&lock);shared_variable++;printf("Thread %ld: shared_variable = %d\n", (long)pthread_self(), shared_variable);pthread_spin_unlock(&lock);sleep(1); // 模拟耗时操作}return NULL;
}int main() {pthread_t thread1, thread2;// 初始化自旋锁pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE);// 创建线程pthread_create(&thread1, NULL, thread_func, NULL);pthread_create(&thread2, NULL, thread_func, NULL);// 等待线程结束pthread_join(thread1, NULL);pthread_join(thread2, NULL);// 销毁自旋锁pthread_spin_destroy(&lock);return 0;
}

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

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

相关文章

mysql 查询的一般思路

能用单表优先用单表&#xff0c;即便是需要用group by、order by、limit等&#xff0c;效率一般也比多表高 不能用单表时优先用连接&#xff0c;连接是SQL中非常强大的用法&#xff0c;小表驱动大表建立合适索引合理运用连接条件&#xff0c;基本上连接可以解决绝大部分问题。…

iis下asp.netcore后台定时任务会取消

问题 使用BackgroundService或者IHostedService做后台定时任务的时候部署到iis会出现不定时定时任务取消的问题&#xff0c;原因是iis会定时的关闭网站 解决 应用程序池修改为AlwaysRunning 修改web.config <?xml version"1.0" encoding"utf-8"?…

RedisTemplate方法一览表

数据类型RedisTemplate 方法Redis命令解释应用场景stringopsForValue().set(key, value)SET设置存储在指定 key 下的值存储简单数据&#xff0c;如用户的设置、配置项opsForValue().get(key)GET获取存储在指定 key 下的值读取存储的数据&#xff0c;如用户信息、配置参数opsFor…

Android studio登录Google账号超时的解决方法

确保自己已经打开了代理&#xff08;科学上网&#xff09;在设置-外观与行为-系统设置-HTTP代理 中打开“自动检测代理设置”&#xff1a; 再次重新尝试登录Google账号&#xff0c;登陆成功&#xff01; 学术会议征稿 想要了解国内主办的覆盖学科最全最广的学术会议&#xff0c…

javafx例子笔记

文章目录 创建过程javafx独立版报错 Exception in thread "WindowsNativeRunloopThread" java.lang.NoSuchMethodError: <init> javafx是java gui工具。 一般会转换为exe&#xff0c;成为可交互的应用。 那么来个简单的例子吧。 先说明一点&#xff0c;javafx不…

代码-功能-python-爬取博客网标题作者发布时间

环境&#xff1a; python 3.8 代码&#xff1a; # 爬取博客园内容 # https://www.cnblogs.com/import re from lxml import etree import requests import json import threading from queue import Queue import pymysql import timeclass HeiMa:def __init__(self):# 请…

k8s 部署 ruoyi 前后端分离项目

本文视频版 https://www.bilibili.com/video/BV17ugkePEeN 参考 https://blog.csdn.net/qq_50247813/article/details/136934090 https://gitee.com/nasaa/RuoYi-Vue-cloud https://www.itsgeekhead.com/tuts/kubernetes-129-ubuntu-22-04-3/ https://kubernetes.io/docs/se…

【漏洞复现】畅捷通T+ keyEdit.aspx SQL漏洞

0x01 产品简介 畅捷通 T 是一款灵动&#xff0c;智慧&#xff0c;时尚的基于互联网时代开发的管理软件&#xff0c;主要针对中小型工贸与商贸企业&#xff0c;尤其适合有异地多组织机构(多工厂&#xff0c;多仓库&#xff0c;多办事处&#xff0c;多经销商)的企业&#xff0c;…

VB.NET 取当前项目根命名空间

VB.NET 取当前项目根命名空间 在VB.NET中&#xff0c;可以使用反射来获取当前程序集的入口点类型&#xff08;通常是模块或者主类&#xff09;&#xff0c;然后从这个类型的Assembly信息中获取定义它的程序集的根命名空间。以下是一个获取当前项目根命名空间的方法示例&#x…

用户态协议栈06-TCP三次握手

最近由于准备软件工程师职称考试&#xff0c;然后考完之后不小心生病了&#xff0c;都没写过DPDK的博客了。今天开始在上次架构优化的基础上增加TCP的协议栈流程。 什么是TCP 百度百科&#xff1a;TCP即传输控制协议&#xff08;Transmission Control Protocol&#xff09;是…

LabVIEW程序退出后线程仍在运行问题

LabVIEW程序退出后&#xff0c;线程仍在运行的问题可能源于资源管理不当、未正确终止循环、事件结构未处理、并发编程错误以及外部库调用未结束等方面。本文将从这些角度详细分析&#xff0c;探讨可能的原因和解决方案&#xff0c;并提供预防措施&#xff0c;帮助开发者避免类似…

将知乎专栏文章转换为 Markdown 文件保存到本地

一、参考内容 参考知乎文章代码 | 将知乎专栏文章转换为 Markdown 文件保存到本地&#xff0c;利用代码为GitHub&#xff1a;https://github.com/chenluda/zhihu-download。 二、步骤 1.首先安装包flask、flask-cors、markdownify 2. 运行app.py 3.在浏览器中打开链接&…

已解决javax.management.BadStringOperationException异常的正确解决方法,亲测有效!!!

已解决javax.management.BadStringOperationException异常的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 目录 问题分析 出现问题的场景 报错原因 解决思路 解决方法 分析错误日志 检查字符串值合法性 确认字符串格式 优化代码逻辑 增加…

Trimesh介绍及基本使用

Trimesh介绍及基本使用 Trimesh是一个纯Python 工具库&#xff08;支持3.7版本以上&#xff09;&#xff0c;用于加载和使用三角形Mesh网格&#xff0c;支持多种常见的三维数据格式&#xff0c;如二进制/文本格式的STL、Wavefront OBJ、二进制/文本格式的PLY、GLTF/GLB 2.0、3…

Leetcode 2713. 矩阵中严格递增的单元格数(DFS DP)

Leetcode 2713. 矩阵中严格递增的单元格数 DFS 容易想到&#xff0c;枚举每个点作为起点&#xff0c;向同行同列的可跳跃点dfs&#xff0c;维护全局变量记录可达的最远距离 超时&#xff0c;通过样例193 / 566 class Solution {int res 0;public void dfs(int[][] mat, in…

EtherCAT笔记(三) —— 主站与从站的硬件组成

1. EtherCAT 主站的硬件组成 EtherCAT主站使用标准以太网控制器&#xff0c;也即EtherCAT主站可以使用以太网控制器的任何设备。当我们有一台带网口的笔记本、工控机&#xff0c;甚至是树莓派也可以作为EtherCAT主站。 EtherCAT协议是对Ethernet协议在实时控制等方面的优化&am…

android关于源码编译简单的apk处理

文章目录 简述文件的添加 简述 创建AOSP源码可编译一个简单apk的过程&#xff0c;代码子目录结构图如下所示 文件的添加 1.com.custom.test目录下创建TestActivity.java文件 用于简单的界面显示类 package com.custom.test;import android.app.Activity; import android.o…

java springboot 正合Knife4j框架

要将 Knife4j&#xff08;之前称为 SwaggerBootstrapUi&#xff09;集成到 Spring Boot 项目中。 可以按照以下步骤操作&#xff1a; 1、添加依赖 在 pom.xml 文件中添加 Knife4j 的相关依赖。确保 Spring Boot 版本与 Knife4j 支持的版本相匹配。 这里以 Maven 为例…

高考填报志愿不容易,压线考生怎么救?

每年的高考季 就是高考生们水深火热的一大月份&#xff0c;很多考生都会纠结要报考哪些学校&#xff0c;哪些专业好&#xff0c;并非每个学生从小就有明确的目标&#xff0c;很多人到6月份才深思这个问题&#xff0c;此时难免手慌脚乱&#xff0c;更别说一些考生的分数处于一本…

创建第一个Springboot项目(环境准备、环境存在的问题、启动时存在的问题、启动的方式)

一、环境准备 专业版创建springboot&#xff0c;直接有一个选项可以选择 社区版&#xff0c;需要下载一个spring的插件 不要直接点 install 因为这个插件是付费的&#xff0c;直接点安装只有30天使用期限 在里面找免费版本的下载 然后安装 安装完成后&#xff0c;这个插件名会变…