Linux-笔记 线程同步机制

2024-06-24 08:04

本文主要是介绍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;
}

这篇关于Linux-笔记 线程同步机制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1089579

相关文章

Linux在线解压jar包的实现方式

《Linux在线解压jar包的实现方式》:本文主要介绍Linux在线解压jar包的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux在线解压jar包解压 jar包的步骤总结Linux在线解压jar包在 Centos 中解压 jar 包可以使用 u

linux解压缩 xxx.jar文件进行内部操作过程

《linux解压缩xxx.jar文件进行内部操作过程》:本文主要介绍linux解压缩xxx.jar文件进行内部操作,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、解压文件二、压缩文件总结一、解压文件1、把 xxx.jar 文件放在服务器上,并进入当前目录#

Linux系统性能检测命令详解

《Linux系统性能检测命令详解》本文介绍了Linux系统常用的监控命令(如top、vmstat、iostat、htop等)及其参数功能,涵盖进程状态、内存使用、磁盘I/O、系统负载等多维度资源监控,... 目录toppsuptimevmstatIOStatiotopslabtophtopdstatnmon

Android ClassLoader加载机制详解

《AndroidClassLoader加载机制详解》Android的ClassLoader负责加载.dex文件,基于双亲委派模型,支持热修复和插件化,需注意类冲突、内存泄漏和兼容性问题,本文给大家介... 目录一、ClassLoader概述1.1 类加载的基本概念1.2 android与Java Class

Javaee多线程之进程和线程之间的区别和联系(最新整理)

《Javaee多线程之进程和线程之间的区别和联系(最新整理)》进程是资源分配单位,线程是调度执行单位,共享资源更高效,创建线程五种方式:继承Thread、Runnable接口、匿名类、lambda,r... 目录进程和线程进程线程进程和线程的区别创建线程的五种写法继承Thread,重写run实现Runnab

SpringBoot线程池配置使用示例详解

《SpringBoot线程池配置使用示例详解》SpringBoot集成@Async注解,支持线程池参数配置(核心数、队列容量、拒绝策略等)及生命周期管理,结合监控与任务装饰器,提升异步处理效率与系统... 目录一、核心特性二、添加依赖三、参数详解四、配置线程池五、应用实践代码说明拒绝策略(Rejected

在Linux中改变echo输出颜色的实现方法

《在Linux中改变echo输出颜色的实现方法》在Linux系统的命令行环境下,为了使输出信息更加清晰、突出,便于用户快速识别和区分不同类型的信息,常常需要改变echo命令的输出颜色,所以本文给大家介... 目python录在linux中改变echo输出颜色的方法技术背景实现步骤使用ANSI转义码使用tpu

linux hostname设置全过程

《linuxhostname设置全过程》:本文主要介绍linuxhostname设置全过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录查询hostname设置步骤其它相关点hostid/etc/hostsEDChina编程A工具license破解注意事项总结以RHE

Spring事务传播机制最佳实践

《Spring事务传播机制最佳实践》Spring的事务传播机制为我们提供了优雅的解决方案,本文将带您深入理解这一机制,掌握不同场景下的最佳实践,感兴趣的朋友一起看看吧... 目录1. 什么是事务传播行为2. Spring支持的七种事务传播行为2.1 REQUIRED(默认)2.2 SUPPORTS2

Linux中压缩、网络传输与系统监控工具的使用完整指南

《Linux中压缩、网络传输与系统监控工具的使用完整指南》在Linux系统管理中,压缩与传输工具是数据备份和远程协作的桥梁,而系统监控工具则是保障服务器稳定运行的眼睛,下面小编就来和大家详细介绍一下它... 目录引言一、压缩与解压:数据存储与传输的优化核心1. zip/unzip:通用压缩格式的便捷操作2.