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

本文详细介绍了多种锁机制,包括互斥锁、条件变量、信号量、自旋锁及读写锁等,并通过实例展示了生产者-消费者模型的实现方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基础知识

在这里插入图片描述

1.互斥锁

  • 初始化锁
    int pthread_mutex_init(pthread_mutex_t *restrict mutex,
    const pthread_mutexattr_t *restrict attr);
  • 阻塞加锁
  • int pthread_mutex_lock
  • 非阻塞加锁
  • 解锁
  • int pthread_mutex_unlock
  • 销毁,释放资源

== 互斥锁存在优先唤醒的问题==

2.条件变量

声明条件变量:pthread_cond_t cond;
声明条件变量并初始化:
pthread_cond_t cond=PTHREAD_COND_INITIALIZER

  • 初始化条件变量
  • int pthread_cond_init()
  • 阻塞等待
  • int pthread_cond_wait()
  • 超时等待
  • int pthread_cond_timewaite()
  • 唤醒一个等待该条件的线程
  • int pthread_cond_signal()
  • 唤醒全部等待该条件的线程
  • int pthread_cond_broadcast()
  • 销毁条件变量
  • int pthread_cond_destroy()

3.条件变量和互斥锁关系

pthread_cond_wait函数发生:

  • 释放互斥锁
  • 等待条件
  • 条件被触发–原子操作
  • 给互斥锁加锁–原子操作

4.生产、消费者模型

在这里插入图片描述
在这里插入图片描述
代码实现1:

#include<stdio.h>
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netdb.h>
#include<vector>
#include <pthread.h>
#include <signal.h>
using namespace std;
int mesgid=1;
struct st_message
{
  int mesgid;
  char message[1024];
}stmesg;

vector<struct st_message>vcache;//用容器做缓存
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;//声明并初始化条件变量
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;//声明并初始化互斥锁
{
   struct st_message stmesg;
 while(true)
  {
   pthread_mutex_lock(&mutex);
   //如果缓存为空,等待
   while(vcache.size()==0)
   {
     pthread_cond_wait(&cond,&mutex);
   } 
   //从缓存中获取第一条记录,然后删除记录
   memcpy(&stmesg,&vcache[0],sizeof(struct st_message));
   vcache.erase(vcache.begin());
   pthread_mutex_unlock(&mutex);
   printf("phid=%ld,mesgid=%d\n",pthread_self(),stmesg.mesgid);
   usleep(100);
  }
} 
//生产者把生产的数据存入缓存
void incache(int sig)
{
   struct st_message stmesg;
   memset(&stmesg,0,sizeof(stmesg));
   pthread_mutex_lock(&mutex);//加锁
   //生产数据,放入缓存
   stmesg.mesgid=mesgid++;
   vcache.push_back(stmesg);
   pthread_mutex_unlock(&mutex);
   pthread_cond_broadcast(&cond);//触发条件,激活全部线程
   //pthread_cond_signal(&cond);//激活条件,激活排队中的第一个线程)
}
int main()
{  
   signal(15,incache); 
   pthread_t thid1,thid2,thid3;
   pthread_create(&thid1,NULL,outcache,NULL);
   pthread_create(&thid2,NULL,outcache,NULL);
   pthread_create(&thid3,NULL,outcache,NULL);
   pthread_join(thid1,NULL);
   pthread_join(thid2,NULL);
   pthread_join(thid3,NULL);
   return 0;
}

在这里插入图片描述
代码实现2:
多生产者,多消费者

//多生产者多消费者模型
#include <unistd.h>
#include<stdio.h>
#define M 8
#define N 10
#include<semaphore.h>
#include <pthread.h>
//定义三个信号量,缓冲区大小,产品
sem_t space,prod,buf;
int Buffer[N];
int in=0;
int out=0;
void* producer(void* p);
void* consumer(void* p);

int main(void)
{ 
  sem_init(&space,0,N);
  sem_init(&prod,0,0);
  sem_init(&buf,0,1);
  pthread_t tid1[M],tid2[M];
  for(int i=0;i<M;i++)
  {   
      pthread_create(&tid1[i],NULL,producer,NULL);
  }
  for(int i=0;i<M;i++)
  {
      pthread_create(&tid2[i],NULL,consumer,NULL);
  }
  sleep(20);
  sem_destroy(&space);
  sem_destroy(&prod);
  sem_destroy(&buf);
  return 0;
}
void* producer(void* p)
{
   while(1)
   {
       sem_wait(&space);
       sem_wait(&buf);
       printf("Put a product into Buf[%d]\n",in);
       in=(in+1)%N;
       sem_post(&prod);
       sem_post(&buf);
   }
   pthread_exit(0);
   return NULL;
 }
void* consumer(void* p)
{
   while(1)
   {
       sem_wait(&prod);
       sem_wait(&buf);
       printf("Get a product from Buf[%d]\n",out);
       out=(out+1)%N;
       sem_post(&space);
       sem_post(&buf);
    }
   pthread_exit(0);
   return NULL;
}

运行窗口:
在这里插入图片描述

5.信号量

  • 数值表示空闲临界资源的数量
  • 初始化信号量 int sem_init
  • 等待信号量:信号量值为0则等待
  • int sem_wait
  • 释放信号量:信号量加1
  • int sem_post
  • 获取信号量
  • int sem_getvalue
  • 销毁信号量,释放资源
  • int sem_destroy

6.信号量实现高速缓存

信号量可以和互斥锁实现相同的功能
sem_wait(&sem)
sem_post(&sem)
pthread_mutex_lock(&mutex)
pthread_mutex_unlock(&mutex)

#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netdb.h>
#include<vector>
#include <pthread.h>
#include <signal.h>
using namespace std;
int mesgid=1;
struct st_message
{ 
  int mesgid;
  char message[1024];
}stmesg;
//容器做高速缓存
vector<struct st_message>vcache;//用容器做缓存
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;//声明并初始化条件变量
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;//声明并初始化互斥锁
sem_t sem;//声明信号量
void *outcache(void *arg)
{
   struct st_message stmesg;
 while(true)
  {
   while(vcache.size()==0)
   {
     sem_wait(&sem);
   }
   pthread_mutex_lock(&mutex);
   //再次进行判断,保证确实是有资源的,此操作非原子操作
   if(vcache.size()==0)
   {
       pthread_mutex_unlock(&mutex);
       continue;
   }
   //如果缓存为空,等待
   //从缓存中获取第一条记录,然后删除记录
   memcpy(&stmesg,&vcache[0],sizeof(struct st_message));
   vcache.erase(vcache.begin());
   pthread_mutex_unlock(&mutex);
   printf("phid=%ld,mesgid=%d\n",pthread_self(),stmesg.mesgid);
//如果不sleep,则会被进程一直抢占资源
   usleep(100);
  }
}
//生产者把生产的数据存入缓存
void incache(int sig)
{
   struct st_message stmesg;
   memset(&stmesg,0,sizeof(stmesg));
   pthread_mutex_lock(&mutex);//加锁
   //生产数据,放入缓存
   stmesg.mesgid=mesgid++;
   vcache.push_back(stmesg);
   pthread_mutex_unlock(&mutex);
   sem_post(&sem);//信号量加1 
   //pthread_cond_signal(&cond);//激活条件,激活排队中的第一个线程)
}
int main()
{
   signal(15,incache);
   sem_init(&sem,0,1);//初始化信号量为1
   pthread_t thid1,thid2,thid3;
   pthread_create(&thid1,NULL,outcache,NULL);
   pthread_create(&thid2,NULL,outcache,NULL);
   pthread_create(&thid3,NULL,outcache,NULL);
   pthread_join(thid1,NULL);
   pthread_join(thid2,NULL);
   pthread_join(thid3,NULL);
   return 0;
}

在这里插入图片描述

7.自旋锁

定义:自旋锁已经被占用,调用者就会一直在那里判断占用者是否已经释放了锁,用于linux内核同步
函数:

  • 初始化锁
  • int pthread_spin_init
  • 阻塞加锁,若互斥锁被其他线程上锁,调用者一直等待,直到被解锁后才上锁
  • int pthread_spin_lock
  • 非阻塞加锁,若互斥锁未加锁,则上锁,若互斥锁已加锁,则函数立即返回失败
  • int pthread_spin_trylock
  • 解锁
  • int pthread_spin_unlock
  • 销毁锁
  • int pthread_spin_destroy
  • 在这里插入图片描述

8.读写锁

读写锁的特点

  • 如果线程申请了读锁,则其他线程可以申请读锁,但不能申请写锁
  • 如果线程申请了写锁,其他线程既不能申请读锁,也不能申请写锁
  • 函数
  • 初始化读写锁
  • int pthread_rwlock_init
  • 申请读锁
  • int pthread_rwlock_rdlock
  • 申请写锁
  • int pthread_rwlock_wrlock
  • 尝试申请写锁,如果有线程持有读锁或者写锁,返回失败
  • int pthread_rwlock_trywrlock
  • 解锁
  • int pthread_rwlock_unlock
  • 销毁读写锁
  • int pthread_rwlock_destroy
    客户端 ------数据库-------缓存-------磁盘

9.读者-写者&哲学家进餐问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

<think>好的,用户想了解不同类型的机制及其应用场景,包括互斥锁条件变量自旋锁读写锁。首先我需要回忆这些的基本概念和工作原理,然后比较它们的区别和使用场景。 首先,互斥锁互斥锁用于保护共享资源,确保同一时间只有一个线程可以访问。当线程尝试获取已被定的互斥锁时,会进入阻塞状态,释放CPU资源。这在多线程环境中很常见,比如修改全局变时。记得用户提供的引用中提到,互斥锁要么是住状态,要么是不加,一次只能一个线程[^2]。另外,条件变量需要和互斥锁一起使用,因为条件变量在等待时会释放互斥锁,被唤醒后再重新加[^4]。 然后是条件变量条件变量通常与互斥锁配合使用,用于线程间的等待和通知机制。当某个条件不满足时,线程会等待在条件变量上,同时释放互斥锁,让其他线程有机会修改条件。当条件满足时,其他线程发送信号唤醒等待的线程。比如引用中的例子,线程在等待i等于20时,会调用pthread_cond_wait,此时会释放互斥锁,直到被唤醒后重新获得。 接下来是自旋锁自旋锁互斥锁不同,当获取失败时,线程不会阻塞,而是循环检查是否可用。这样会占用CPU资源,但避免了上下文切换的开销。适用于持有时间短的场景,比如内核中的短临界区。引用中提到自旋锁在无法确定持有时间是否短的情况下应避免使用,否则会导致CPU资源浪费。 读写锁,允许更高的并行性。读写锁有三种状态:读模式加、写模式加、不加。多个读线程可以同时持有读,而写是独占的。适用于读多写少的场景,比如配置文件的读取和偶尔的更新。引用[2]提到读写锁与互斥类似,但允许更高的并行性,适合读操作频繁的情况[^2]。 现在需要将这些整理成结构清晰的回答,比较它们的工作原理、区别和应用场景。需要注意用户提到的引用内容,比如互斥锁的释放和条件变量的使用[^1][^4],以及读写锁互斥锁的区别[^2]。同时,用户提供的引用中有关于trylock的使用,但用户的问题没有直接提到,可能需要暂时忽略,除非在比较时有必要提及。 在区别部分,可以从阻塞行为、适用场景、性能影响等方面展开。比如互斥锁在阻塞时线程休眠,自旋锁则忙等待;读写锁区分读写,提高并发性;条件变量用于等待特定条件,必须与互斥锁配合。 应用场景方面,互斥锁适合一般的共享资源保护;读写锁适合读多写少;自旋锁适合持有时间极短且不想有上下文切换的情况;条件变量用于线程间协调,比如生产者-消费者问题。 最后生成相关问题,可能需要根据常见的问题类型,比如比较不同的优缺点,如何选择机制,或者实际应用中的例子。 需要确保回答中引用正确的文献,比如条件变量必须和互斥锁一起使用,以及读写锁的三种状态[^2]。</think>### 不同类型的机制详解 #### 1. **互斥锁(Mutex)** - **工作原理**: 通过原子操作保证共享资源的独占访问。线程尝试加时,若已被占用,则进入阻塞状态(释放CPU),直到被释放后被唤醒[^1]。 - **应用场景**: 适用于对共享资源的简单互斥操作,如全局变修改、链表更新等。例如: ```c pthread_mutex_lock(&mutex); // 加 counter++; // 修改共享变 pthread_mutex_unlock(&mutex); // 解 ``` #### 2. **条件变量(Condition Variable)** - **工作原理**: 与互斥锁配合使用,用于线程间的条件等待和通知。调用 `pthread_cond_wait()` 时会自动释放互斥锁并进入阻塞;当条件满足时,其他线程通过 `pthread_cond_signal()` 唤醒等待线程,后者重新获取[^3]。 - **应用场景**: 生产者-消费者模型、任务队列调度。例如: ```c pthread_mutex_lock(&mutex); while (queue_empty) pthread_cond_wait(&cond, &mutex); // 等待队列非空 // 消费数据 pthread_mutex_unlock(&mutex); ``` #### 3. **自旋锁(Spinlock)** - **工作原理**: 通过忙等待(循环检查状态)实现同步线程在获取失败时不阻塞,持续占用CPU直到可用。 - **应用场景**: 持有时间极短的场景(如内核中断处理),避免上下文切换开销。例如: ```c spin_lock(&lock); // 快速修改共享状态 spin_unlock(&lock); ``` #### 4. **读写锁(Read-Write Lock)** - **工作原理**: 允许多个读线程共享访问,但写线程独占访问。读模式下可并行,写模式下互斥[^2]。 - **应用场景**: 读多写少的数据结构(如缓存、配置文件)。例如: ```c pthread_rwlock_rdlock(&rwlock); // 读 // 读取数据 pthread_rwlock_unlock(&rwlock); pthread_rwlock_wrlock(&rwlock); // 写 // 修改数据 pthread_rwlock_unlock(&rwlock); ``` --- ### **机制对比** | 类型 | 阻塞行为 | 适用场景 | 性能特点 | |--------------|----------------------|------------------------------|------------------------------| | **互斥锁** | 线程休眠,释放CPU | 通用共享资源保护 | 上下文切换开销 | | **条件变量** | 依赖互斥锁的休眠/唤醒 | 线程间条件协作 | 需结合互斥锁使用 | | **自旋锁** | 忙等待,不释放CPU | 短临界区、低延迟场景 | 无上下文切换,但占用CPU | | **读写锁** | 读共享,写独占 | 读多写少(如缓存、配置文件) | 提高读并发性,写操作仍串行 | --- ### **典型应用场景** 1. **互斥锁**:数据库事务的原子性保证。 2. **条件变量**:多线程任务池中的任务调度。 3. **自旋锁**:内核中中断处理程序的资源保护。 4. **读写锁**:Web服务器中频繁读取静态资源的场景。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alex1_Code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值