不到断气不罢休 发表于 2024-10-25 11:35:12

Linux的线程安全(同步和互斥,信号量,互斥锁,读写锁)

#1024程序员节 | 征文# 
 https://i-blog.csdnimg.cn/direct/b81a1c43a184480dbca94cae852e61d2.png
 
目次
 引入
有名信号量
相干api函数原型
创建一个信号量并获取其值
 PV操作加减信号量
信号量实现线程同步
无名信号量 
互斥锁
利用互斥锁实现两线程瓜代执行 
读写锁

 引入

        当多个线程或者多个进程共同访问同一共享资源时,会出现竞争(抢占)问题,这时候我们就要采取信号量/互斥锁/读写锁来控制线程与进程之间的互斥与同步。
        同步是指多个进程或线程为了完成某个任务而协调它们的执行顺序和时间的机制。它确保了不同的执行单元(进程或线程)在精确的时间执行精确的操作,以实现对共享资源或共享任务的精确处理。在线程同步问题中引入经典操作体系模型生产者-消费者问题举行介绍
        生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了共享固定巨细缓冲区的两个线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的紧张作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区斲丧这些数据。该问题的关键就是要包管生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时斲丧数据。
        要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),比及下次消费者斲丧缓冲区中的数据的时候,生产者才能被叫醒,开始往缓冲区添加数据。
        同样,也可以让消费者在缓冲区空时进入休眠,比及生产者往缓冲区添加数据之后,再叫醒消费者。通常采取进程间通信的方法解决该问题。如果解决方法不够完满,则容易出现死锁的环境。出现死锁时,两个线程都会陷入休眠,期待对方叫醒本身。该问题也能被推广到多个生产者和消费者的情形。
参考文献:【操作体系】生产者消费者问题-CSDN博客
有名信号量

        Linux中用到的信号量有3种:ststem-V 信号量、POSIX有名信号量和 POSIX无名信号量。他们固然有很多显著不同的地方,但是最根本的功能室一致的:用来表征一种资源的数量,当多个进程或者线程夺取这些稀缺资源的时候,信号量用来包管他们合理地、秩序地使用这些资源,而不会陷入逻辑谬误之中,POSIX信号量固然没有system V使用的更广泛,这紧张是由于system V机制发源于unix足够古老,POSIX会更加的简便和好用,在更新的尺度中已经遍及,所以介绍紧张以POSIX为主
信号量用来同步,锁用来互斥
POSIX有名信号量的一样平常使用步骤是:
1,使用 sem_open( )来创建或者打开一个有名信号量。
2,使用 sem_wait( )和 sem_post( )来分别举行P操作和V操作。
3,使用 sem_close( )来关闭他。
4,使用 sem_unlink( )来删除他,并释放体系资源。
 
相干api函数原型

初始化或打开一个有名信号量 
#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,
                       mode_t mode, unsigned int value);

name:信号量的文件名,必须以正斜杠/开头
oflag:权限  
O_CREAT 创建  
O_EXCL  检查是否存在                                                                                                            mode:信号量文件的权限  
 value:信号量的初始值  
返回值:乐成  返复书号量地址    
            失败   SEM_FAILED 

#include <semaphore.h>
int sem_getvalue(sem_t *sem, int *sval); //获取一个信号量的值
sem:信号量指针 
sval:值
int sem_close(sem_t *sem);  //关闭信号量
int sem_unlink(const char *name);  //销毁有名信号量
创建一个信号量并获取其值

#include <stdio.h>
#include <fcntl.h>   
#include <sys/stat.h>
#include <semaphore.h>
int main()
{
    // 创建一个信号量
    sem_t *sem = sem_open("/loading", O_CREAT, 0777, 20);
    if (sem == SEM_FAILED)
    {
      perror("创建信号量失败\n");
      return -1;
    }

    // 获取该信号量的值
    int value = 0;
    sem_getvalue(sem, &value);
    printf("%d\n", value);
}

        这种有名信号量的名字由类似“/somename”这样的字符串组成,留意前面有一个正斜杠,这样的信号量着实是一个特殊的文件,创建乐成之后将会被放置在体系的一个特殊的假造文件体系/dev/shm 之中,不同的进程间只要约定好一个相同的名字,他们就可以通过这种有名信号量来相互协调。
值得一提的是,有名信号量跟system-V的信号量都是体系范畴的,在进程退出之后他们并不会主动消失,而必要手工删除并释放资源。
 信号量创建后就储存在/dev/shm中
https://i-blog.csdnimg.cn/direct/26bbc606e7694281b4cfc8a318de5469.png

P操作(信号量减一)
#include <semaphore.h>
int sem_wait(sem_t *sem);
sem:操作的信号量指针 
返回值: 乐成   0   
             失败   -1

V操作(信号量加一)
#include <semaphore.h>
int sem_post(sem_t *sem);
sem:操作的信号量指针 
返回值: 乐成   0   
             失败   -1

 PV操作加减信号量

#include <stdio.h>
#include <fcntl.h>    /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>

#define P 0 // P为1时,循环进行减操作
#define V 1 // V为1师,循环进行加操作

int main()
{
    // 创建一个信号量
    sem_t *sem = sem_open("/home", O_CREAT, 0777, 5);
    if (sem == SEM_FAILED)
    {
      perror("创建信号量失败\n");
      return -1;
    }

    // 获取信号量的值
    int value = 0;
    sem_getvalue(sem, &value);
    printf("%d\n", value);

    // 进行P操作
    sem_wait(sem);
    // 获取值
    sem_getvalue(sem, &value);
    printf("%d\n", value);

    while (1)
    {
#if V
      // 进行V 操作
      sem_post(sem);
      // 获取值
      sem_getvalue(sem, &value);
      printf("%d\n", value);
#endif

#if P
      // 进行P操作 */
      sem_wait(sem);
      // 获取值
      sem_getvalue(sem, &value);
      printf("%d\n", value);
#endif
    }
}
信号量实现线程同步

#include <stdio.h>
#include <pthread.h>
#include <fcntl.h>   
#include <sys/stat.h>
#include <semaphore.h>

int value = 0; // 全局变量,所有线程共享
sem_t *sem;
sem_t *sem1;
// 子线程
void *task(void *arg)
{
    while (1)
    {
      sem_wait(sem);
      printf("子线程增加\n");
      value++;
      sem_post(sem1);
    }
}

int main()
{
    // 创建两个信号量
    sem = sem_open("/sem", O_CREAT, 0777, 1);
    sem1 = sem_open("/sem1", O_CREAT, 0777, 0);

    pthread_t tid;
    pthread_create(&tid, NULL, task, NULL);

    while (1)
    {
      // P操作
      sem_wait(sem1);
      printf("主线程输出:%d\n", value);
      // V操作
      sem_post(sem);
    }

    // 关闭并删除信号量
    sem_close(sem);
    sem_close(sem1);
    sem_unlink("/sem");
    sem_unlink("/sem1");

    return 0;
} 无名信号量 

创建一个无名信号量
无名信号量和有名信号量的区别是有名信号量用于(线程/进程)间的同步互斥,而无名信号量只能用于线程间的同步互斥,如果使用场景只有线程的话,用无名信号量会更加的轻量级,由于无名信号量是基于内存而不存在于任何文件体系内部的。
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_wait(sem_t *sem);   //P操作,资源 -1 
int sem_post(sem_t *sem);    //V操作,资源 +1 
int sem_destroy(sem_t *sem);//销毁一个无名信号量
sem:信号量指针 
pshared:  共享范围    
            0   线程共享   
           非0  父子进程共享
value:无名信号量的初值 
返回值:  0  乐成  
             -1  失败 

互斥锁

        如果信号量的值最多为 1,那实际上相称于一个共享资源在任意时候最多只能有一个线程在访问,这样的逻辑被称为“互斥”。这时,有一种更加方便和语义更加准确的工具来满足这种逻辑,他就是互斥锁。
        互斥锁和读写锁的使用都是初始化、加锁、解锁、销毁。
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);  //初始化互斥锁
int pthread_mutex_lock(pthread_mutex_t *mutex);  //上锁 ,如果没有解锁,上锁会壅闭 
int pthread_mutex_unlock(pthread_mutex_t *mutex); //解锁,解锁可以不停举行,不会壅闭
int pthread_mutex_destroy(pthread_mutex_t *mutex);
mutex:互斥锁 
attr:属性默以为 NULL 即可
返回值: 乐成  0  
             失败  -1 
利用互斥锁实现两线程瓜代执行 

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

// 声明互斥锁
pthread_mutex_t mutex;
pthread_mutex_t mutex2;

// 写入线程
void *task(void *arg)
{
    while (1)
    {
      // 2.上锁
      pthread_mutex_lock(&mutex);
      // 3.访问共享资源
      printf("线程1正在运行\n");
      sleep(1);
      // 4.解锁
      pthread_mutex_unlock(&mutex2);
      ;
    }
}

void *task2(void *arg)
{
    while (1)
    {
      // 2.上锁
      pthread_mutex_lock(&mutex2);
      sleep(1);
      // 3.访问共享资源
      printf("线程2正在运行\n");
      // 4.解锁
      pthread_mutex_unlock(&mutex);
    }
}

int main()
{

    // 1.初始化互斥锁
    pthread_mutex_init(&mutex, NULL);
    pthread_mutex_init(&mutex2, NULL);

    pthread_t tid;
    pthread_create(&tid, NULL, task, NULL);

    pthread_t tid1;
    pthread_create(&tid1, NULL, task2, NULL);

    // 阻塞主进程
    getchar();
} 读写锁

        由于互斥对于共享资源在某一个时间段,只能由一个人去访问,这样的访问效率是比较低的。 所以对于一个共享资源,应该只能由一个人写入,多个人访问这样的效率才高。  
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); //初始化读写锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); //写锁,在写入数据之前添加写锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); //读锁,在读取数据之前添加读锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); //解锁
rwlock:读写锁 
attr:属性默以为  NULL 
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

int value = 0;

pthread_rwlock_t rwlock;

// 写入线程
void *write_task(void *arg)
{
    while (1)
    {
      // 2.上写锁
      pthread_rwlock_wrlock(&rwlock);
      // 3.访问共享资源
      value++;
      sleep(1);
      // 4.解除写锁
      pthread_rwlock_unlock(&rwlock);
    }
}

void *write_task2(void *arg)
{
    while (1)
    {
      // 2.上写锁
      pthread_rwlock_wrlock(&rwlock);
      sleep(1);
      // 3.访问共享资源
      value = 100;
      // 4.解除写锁
      pthread_rwlock_unlock(&rwlock);
    }
}

int main()
{

    // 1.初始化读写锁
    pthread_rwlock_init(&rwlock, NULL);

    pthread_t tid;
    pthread_create(&tid, NULL, write_task, NULL);

    pthread_t tid1;
    pthread_create(&tid1, NULL, write_task2, NULL);

    // 读取数据
    while (1)
    {
      // 1.上读锁
      pthread_rwlock_rdlock(&rwlock);
      // 2.访问数据
      printf("value=%d\n", value);
      // 3.解锁
      pthread_rwlock_unlock(&rwlock);
    }
} 在这个例子中读锁和写锁可以同步发生,只要加了读锁,也可以出现一个线程写锁,多个线程同时读锁的环境,如果把读写锁改成互斥锁那么程序就会一直被壅闭。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Linux的线程安全(同步和互斥,信号量,互斥锁,读写锁)