线程的同步与互斥机制

不同的进程之间通信是通过IPC通信机制来实现通信

进程间的IPC通信机制基本通信方式有七种

1、管道(pipe)

2、有名管道(mkfifo)

3、消息队列(msqid)

4、磁盘映射(mmap)

5、共享内存段(shmid)

6、套接字(socket)

7、信号(SIGNAL)

而多线程相对于一个进程而言,在一个进程中,多个线程之间是直接共享进程的资源的(除了线程非共享的部分),好处是不需要设置什么通信机制,提高了通信效率,但是也有弊端,多个线程之间怎么合理的通信呢?通过线程的同步与互斥机制可以实现合理访问多线程之间的共享数据。

同步与互斥机制说白了就是为了解决多线程之间通信冲突。

同步与互斥描述的是在同一时刻只有一个任务在运行。同步指的是在同一时刻只有一个任务在运行,但是每个任务的运行顺序是有先后之分,而互斥指的是同一时刻只有一个任务在运行,但是哪个任务先执行没有顺序。

同步与互斥机制:

目录

同步与互斥机制:

一、互斥锁

 二、读写锁

 三、条件变量

 四、信号量

4.0 信号量

4.1 无名信号量

 4.2 有名信号量


一、互斥锁

 互斥锁 用于多线程的互斥,    互斥英文名mutex,又称为互斥量,它是一种通过简单的加锁方法来控制多线程对进程中的共享资源的访问,互斥锁有两种状态

互斥锁的创建步骤:

 1、定义互斥锁的类型

pthread_mutex_t

2、互斥锁的初始化

//互斥锁的初始化有两种方法
//动态法

#include <pthread.h>
  int pthread_mutex_init
    (
        pthread_mutex_t *restrict mutex,
        const pthread_mutexattr_t *restrict attr  

/*restrict,C 语言中的一种类型限定符(Type Qualifiers),用于告诉编译器,对象已经被指针所引
用,不能通过除该指针外所有其他直接或间接的方式修改该对象的内容*/

    );

功能:
    初始化一个互斥锁。
参数:
    mutex:互斥锁地址。类型是 pthread_mutex_t 。
    attr:设置互斥量的属性,通常可采用默认属性,即可将 attr 设为 NULL。

返回值:
    成功:0,成功申请的锁默认是打开的。
    失败:非 0 错误码



//静态法
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

这种方法等价于使用 NULL 指定的 attr 参数调用 pthread_mutex_init() 来完成动
态初始化,不同之处在于 PTHREAD_MUTEX_INITIALIZER 宏不进行错误检查

3、上锁和解锁操作


//上锁
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:
    对互斥锁上锁,若互斥锁已经上锁,则调用者阻塞,直到互斥锁解锁后再上锁。
参数:
    mutex:互斥锁地址。
返回值:
    成功:0
    失败:非 0 错误码
    
int pthread_mutex_trylock(pthread_mutex_t *mutex);
    调用该函数时,若互斥锁未加锁,则上锁,返回 0;
    若互斥锁已加锁,则函数直接返回失败,即 EBUSY

//解锁
#include <pthread.h>
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:
    对指定的互斥锁解锁。
参数:
    mutex:互斥锁地址。
返回值:
    成功:0
    失败:非 0 错误码

4、销毁互斥锁


#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:
    销毁指定的一个互斥锁。互斥锁在使用完毕后,必须要对互斥锁进行销毁,以释放资
源。
参数:
    mutex:互斥锁地址。
返回值:
    成功:0
    失败:非 0 错误

案例:mutex.c

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

// 定义一个互斥锁
pthread_mutex_t mutex;
void printf_string(char *str)
{
    while (*str != '\0')
    {
        printf("%c", *str++);
        fflush(stdout);
        sleep(1);
    }
    return;
}

void *my_func(void *arg)
{
    // 上锁
    pthread_mutex_lock(&mutex);//如果这里不上锁,两个线程共享进程资源就会相互抢占,造成输入的数据紊乱
    printf_string(arg);

    // 解锁
    pthread_mutex_unlock(&mutex);
    return NULL;
}
void *my_func1(void *arg)
{
    // 上锁
    pthread_mutex_lock(&mutex);
    printf_string(arg);

    // 解锁
    pthread_mutex_unlock(&mutex);
    return NULL;
}
int main(int argc, char const *argv[])
{

    // 互斥锁的初始化
    pthread_mutex_init(&mutex, NULL);

    // 创建两个线程为例
    // 从终端获取数据
    char name[128] = "";
    fgets(name, sizeof(name), stdin);

    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, my_func, name);
    pthread_create(&tid2, NULL, my_func1, name);

    int num = pthread_join(tid1, NULL);
    if (num == 0)
    {
        printf("线程资源已被回收\n");
    }
    pthread_join(tid2, NULL);

    // 销毁互斥锁
    int num1 = pthread_mutex_destroy(&mutex);
    if (num1 == 0)
    {
        printf("互斥锁已经销毁\n");
    }

    return 0;
}

输出结果1:上锁和解锁结果

输出结果2:不上锁结果

 使用互斥锁需要注意的是预防死锁:

 二、读写锁

通过互斥锁仅仅是简简单单的多线程互斥,让他们轮流的访问进程的共享资源,缺点是每个线程执行完任务后才会释放互斥锁,让下一个进程访问,在这期间其他的线程只能等待。为了进一步解决这个问题,线程库提供了读写锁

线程对进程的共享资源的访问有读写这两种操作,通过读写锁来进一步的细化,分别对读操作和写操作进行加锁。

读写锁的优点:相对于互斥锁不支持读写并发操作,它支持多个线程同时读取共享资源,而只有一个线程可以写入共享资源。读写锁适用于读多写少的场景,可以提高程序的并发性能,减少线程的等待时间。

读写锁的操作步骤

1、设置读写锁的类型

读写锁的数据类型:pthread_rwlock_t

2、读写锁的初始化

 读写锁的初始化

//同互斥锁一样,读写锁的初始化也有两种,一种是静态法(使用宏在与处理阶段就展开,不进行语法检查,因为没有经过编译阶段),另一种是动态法

//动态法
1#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
功能:
    用来初始化 rwlock 所指向的读写锁。
参数:
    rwlock:指向要初始化的读写锁指针。
    attr:读写锁的属性指针。如果 attr 为 NULL 则会使用默认的属性初始化读写
锁,否则使用指定的 attr 初始化读写锁。

返回值:
    成功:0,读写锁的状态将成为已初始化和已解锁。
    失败:非 0 错误码


//静态法
pthread_rwlock_t my_rwlock = PTHREAD_RWLOCK_INITIALIZER;
//这种方法等价于使用 NULL 指定的 attr 参数调用 pthread_rwlock_init() 来完
成动态初始化,不同之处在于 PTHREAD_RWLOCK_INITIALIZER 宏不进行错误检查。

3、申请读、写锁

//申请读锁
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);//带阻塞
功能:
    以阻塞方式在读写锁上获取读锁(读锁定)。
    如果没有写者持有该锁,并且没有写者阻塞在该锁上,则调用线程会获取读锁。
    如果调用线程未获取读锁,则它将阻塞直到它获取了该锁。一个线程可以在一个读写
锁上多次执行读锁定。
    线程可以成功调用 pthread_rwlock_rdlock() 函数 n 次,但是之后该线程必须调
用 pthread_rwlock_unlock() 函数 n 次才能解除锁定。
参数:
    rwlock:读写锁指针。
返回值:
    成功:0
    失败:非 0 错误码
//尝试申请读锁,不带阻塞
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
用于尝试以非阻塞的方式来在读写锁上获取读锁。
如果有任何的写者持有该锁或有写者阻塞在该读写锁上,则立即失败返回



//申请写锁
#include <pthread.h>
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
功能:
    在读写锁上获取写锁(写锁定)。
    如果没有写者持有该锁,并且没有写者读者持有该锁,则调用线程会获取写锁。
    如果调用线程未获取写锁,则它将阻塞直到它获取了该锁。
参数:
    rwlock:读写锁指针。
返回值:
    成功:0
    失败:非 0 错误码
//尝试申请写锁
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
用于尝试以非阻塞的方式来在读写锁上获取写锁。
如果有任何的读者或写者持有该锁,则立即失败返回。

4、释放和销毁读写锁(释放读写锁是为了让某个线程结束任务后释放锁让其他线程继续访问共享资源,而销毁读写锁是为了清理空间释放资源)

//释放读写锁
#include <pthread.h>
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
功能:
    无论是读锁或写锁,都可以通过此函数解锁。
参数:
    rwlock:读写锁指针。
返回值:
    成功:0
    失败:非 0 错误码



//销毁读写锁
#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
功能:
    用于销毁一个读写锁,并释放所有相关联的资源(所谓的所有指的是由 pthread_rw
lock_init() 自动申请的资源) 。
参数:
    rwlock:读写锁指针。
返回值:
    成功:0
    失败:非 0 错误码

案例:rwlock.c

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

// 定义一个全局数组,用来存放从终端获取的数据,属于进程共享资源
char name[128] = "";
// 定义读写锁
pthread_rwlock_t rwlock;

void *my_func1(void *arg)
{
    while (1)
    {
        // 上读锁
        pthread_rwlock_rdlock(&rwlock);

        printf("任务A读取进程的共享资源其内容是:%s\n", name);

        // 解锁
        pthread_rwlock_unlock(&rwlock);
        sleep(1); // 加延迟以便看到现象
    }
    return NULL;
}
void *my_func2(void *arg)
{
    while (1)
    {
        // 上读锁
        pthread_rwlock_rdlock(&rwlock);

        printf("任务B读取进程的共享资源其内容是:%s\n", name);

        // 解锁
        pthread_rwlock_unlock(&rwlock);
        sleep(1); // 加延迟以便看到现象
    }
    return NULL;
}
void *my_func3(void *arg)
{
    while (1)
    {
        // 上写锁
        pthread_rwlock_wrlock(&rwlock);

        // 从终端获取输入数据
        fgets(name, sizeof(name), stdin);

        // 解锁
        pthread_rwlock_unlock(&rwlock);
        sleep(2);//需要注意的是每一个线程访问进程的共享资源都是公平的,当进程共享资源闲置该进程就会立即上写锁,从而导致阻塞,
    }

    return NULL;
}

int main(int argc, char const *argv[])
{

    // 2、初始化读写锁
    pthread_rwlock_init(&rwlock, NULL);

    // 3、创建线程

    pthread_t tid1_r, tid2_r, tid3_w;
    pthread_create(&tid1_r, NULL, my_func1, NULL);
    pthread_create(&tid2_r, NULL, my_func2, NULL);
    pthread_create(&tid3_w, NULL, my_func3, NULL);

    // 4、回收线程资源
    pthread_join(tid1_r, NULL);
    pthread_join(tid2_r, NULL);
    pthread_join(tid3_w, NULL);

    // 5、销毁读写锁
    pthread_rwlock_destroy(&rwlock);
    return 0;
}

输出结果:

 三、条件变量

 与互斥锁和读写锁不同的是条件变量是用来等待某个设置的条件满足后来解除阻塞,他没有锁的机制,只是利用了阻塞的原理。通常条件变量用来结合互斥锁使用。

条件变量和互斥锁结合使用,相对于仅使用互斥锁而言,有以下优势:
1. 减少CPU的占用:当线程在等待条件变量时,会释放互斥锁。这样,其他线程就可以获得互斥锁并访问共享资源,不需要等待当前线程执行完毕。这样可以减少CPU的占用,提高程序的性能。
2. 避免忙等待:如果使用互斥锁等待共享资源的条件不满足时,线程会一直在循环中等待,这种方式称为忙等待。使用条件变量可以避免忙等待,线程会进入等待状态,直到条件满足时被唤醒。
3. 更加灵活:条件变量可以让线程在等待某个条件满足时进入等待状态,等待其他线程通知条件变量的状态变化。这样可以更加灵活地控制线程的执行顺序和并发性。
 

条件变量的创建步骤

1、定义条件变量

条件变量的类型:pthread_cond_t

2、 条件变量的初始化

//同互斥锁和读写锁一样,条件变量也有静态法和动态法的初始化
//动态法
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,
    const pthread_condattr_t *restrict attr);
功能:
    初始化一个条件变量
参数:
    cond:指向要初始化的条件变量指针。
    attr:条件变量属性,通常为默认值,传 NULL 即可

返回值:
    成功:0
    失败:非 0 错误号

//静态法初始化
也可以使用静态初始化的方法,初始化条件变量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

3、条件变量的条件阻塞设置

//通过等待一个解除条件变量的信号或者广播来解除阻塞
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
功能:
    阻塞等待一个条件变量
        a) 阻塞等待条件变量 cond(参 1)满足
        b) 释放已掌握的互斥锁(解锁互斥量)相当于 pthread_mutex_unlock(&mutex);
        a) b) 两步为一个原子操作。
        c) 当被唤醒,pthread_cond_wait 函数返回时,解除阻塞并重新申请获取互斥锁 p
thread_mutex_lock(&mutex);
参数:
    cond:指向要初始化的条件变量指针
    mutex:互斥锁
返回值:
    成功:0
    失败:非 0 错误号

//通过绝对时间的条件满足来解除阻塞
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
 pthread_mutex_t *restrict mutex,
const struct
*restrict abstime);
功能:
    限时等待一个条件变量
参数:
    cond:指向要初始化的条件变量指针
    mutex:互斥锁
    abstime:绝对时间
返回值:
    成功:0
    失败:非 0 错误号

4、被条件变量阻塞的线程唤醒(解阻塞)

//唤醒一个或多个被条件变量函数阻塞的线程
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
功能:
    唤醒至少一个阻塞在条件变量上的线程
参数
    cond:指向要初始化的条件变量指
返回值
    成功:0
    失败:非 0 错误

//唤醒所有的被条件变量阻塞的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
功能:
    唤醒全部阻塞在条件变量上的线程
参数:
    cond:指向要初始化的条件变量指针
返回值:
    成功:0
    失败:非 0 错误号

 5、销毁条件变量

#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
功能:
    销毁一个条件变量
参数:
    cond:指向要初始化的条件变量指针
返回值:
    成功:0
    失败:非 0 错误号

案例:mutex_cond.c

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

/*功能 模拟消费者和生产者模式,简单来说*/
// 定义一把互斥锁
pthread_mutex_t mutex;
// 定义条件变量
pthread_cond_t cond;
// 模拟商品数量,开始为3个
int num = 3;

void *my_func1(void *arg)
{
    while (1)
    {
        // 上锁
        pthread_mutex_lock(&mutex);
        // 如果没有商品就阻塞
        if (num == 0)
        {
            pthread_cond_wait(&cond, &mutex);
        }
        else
        {
            // 购买商品
            num--;
            printf("这个商品不错,线程1消费者购买了一个\n");
            printf("剩余商品数量为 %d个\n", num);
        }
        // 解锁
        pthread_mutex_unlock(&mutex);

        // 模拟使用商品(商品使用时间不确定)
        sleep(rand() % 3 + 1);
    }
    return NULL;
}
void *my_func2(void *arg)
{
    while (1)
    {
        // 上锁
        pthread_mutex_lock(&mutex);
        // 如果没有商品就阻塞
        if (num == 0)
        {
            pthread_cond_wait(&cond, &mutex);
        }
        else
        {
            // 购买商品
            num--;
            printf("这个商品不错,线程2消费者也购买了一个\n");
            printf("剩余商品数量为 %d个\n", num);
        }
        // 解锁
        pthread_mutex_unlock(&mutex);

        // 模拟使用商品(商品使用时间不确定)
        sleep(rand() % 3 + 1);
    }
    return NULL;
}
void *my_func3(void *arg)
{
    while (1)
    {
        // 模拟生产商品(商品生产时间不确定)
        sleep(rand() % 3 + 1);

        // 上锁(将生产好的商品存储到仓库)
        pthread_mutex_lock(&mutex);
        // 放入商品
        num++;
        printf("生产者线程厂商制造了一个商品,现有库存商品数量为:%d\n", num);
        // 广播条件满足,告知客户,生产厂家准备卖货了,钱准备好
        pthread_cond_broadcast(&cond);

        // 解锁(出仓库)
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}
int main(int argc, char const *argv[])
{
    // 设置随机数种子
    srand(time(NULL));

    // 初始化互斥锁
    pthread_mutex_init(&mutex, NULL);
    // 初始化条件变量
    pthread_cond_init(&cond, NULL);
    // 创建三个线程
    pthread_t tid1_r, tid2_r, tid3_w;
    pthread_create(&tid1_r, NULL, my_func1, NULL);
    pthread_create(&tid2_r, NULL, my_func2, NULL);
    pthread_create(&tid3_w, NULL, my_func3, NULL);

    // 回收线程资源
    pthread_join(tid1_r, NULL);
    pthread_join(tid2_r, NULL);
    pthread_join(tid3_w, NULL);

    // 销毁读写锁
    pthread_mutex_destroy(&mutex);
    // 销毁条件变量
    pthread_cond_destroy(&cond);
    return 0;
}

输出结果:商品比较热销,库存抢光了,商家紧急生产也赶不及

 四、信号量

 信号量本质是一个非负数的整数计数器,用来控制对公共资源的访问(需要注意的是信号量常用于多进程间的通信,而信号量数组用于多线程间的通信)

信号量的特点:

1、信号量大于0,解阻塞,等于0就阻塞

2、PV 原语是对信号量的操作,一次 P 操作使信号量减1,一次 V 操作使信号量加1。

与互斥量和读写锁和条件变量不同的是信号量作用在互斥和同步上有所区别

信号量作用在多线程的互斥:不管有多少任务,只有一个信号量

信号量作用于多线程的同步:有几个任务就需要几个信号量

4.0 信号量

信号量的创建步骤:

1、定义信号量

信号量数据类型为:sem_t

2、初始化信号量

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value)
功能:
    创建一个信号量并初始化它的值。一个无名信号量在被使用前必须先初始化。
参数:
    sem:信号量的地址
    pshared:等于 0,信号量在线程间共享(常用);不等于 0,信号量在进程间共
享。
    value:信号量的初始值
返回值:
    成功:0
    失败: - 1

3、信号量的P和V操作

//信号量的P操作(减)
int sem_wait(sem_t *sem);
功能: 将信号量减一,如果信号量的值为 0 则阻塞,大于 0 可以减一,并解阻塞
参数:信号量的地址
返回值:
    成功返回 0 
    失败返回-1
//尝试P操作
int sem_trywait(sem_t *sem);
功能: 尝试将信号量减一,如果信号量的值为 0 不阻塞,立即返回 ,大于 0 可以减一
参数:信号量的地址
返回值:
    成功返回 0 
    失败返回-1

//信号量的V操作(加)
int sem_post(sem_t *sem);
功能:将信号量加一
参数:信号量的地址
返回值:
    成功返回 0 
    失败返回-1

4、销毁信号量

int sem_destroy(sem_t *sem);
功能: 销毁信号量
参数: 信号量的地址
返回值:成功返回 0 失败返回-1

案例:sem_mutex.c

信号量用于多线程的互斥(只定义一个信号量)

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
// 1、定义信号量
sem_t sem;

void printf_string(char *str)
{
    while (*str)
    {
        printf("%c", *str++);
        fflush(stdout);
        sleep(1);
    }
}

void *my_func1(void *arg)
{
    // P -1
    sem_wait(&sem);
    printf("我是一号线程,我在打印\n");
    printf_string(arg);

    // V +1
    sem_post(&sem);
}
void *my_func2(void *arg)
{
    // P -1
    sem_wait(&sem);
    printf("我是二号线程,我在打印\n");
    printf_string(arg);

    // V +1
    sem_post(&sem);
}
void *my_func3(void *arg)
{
    // P -1
    sem_wait(&sem);
    printf("我是三号线程,我在打印\n");
    printf_string(arg);

    // V +1
    sem_post(&sem);
}
int main(int argc, char const *argv[])
{
    // 2、初始化信号量
    sem_init(&sem, 0, 1);

    // 3、创建三个线程
    pthread_t tid1, tid2, tid3;
    // 从终端获取数据
    printf("请输入线程A的资源:");
    char data1[128] = "";
    fgets(data1, sizeof(data1), stdin);
    pthread_create(&tid1, NULL, my_func1, data1);

    // 从终端获取数据
    printf("请输入线程B的资源:");
    char data2[128] = "";
    fgets(data2, sizeof(data2), stdin);
    pthread_create(&tid2, NULL, my_func2, data2);

    // 从终端获取数据
    printf("请输入线程C的资源:");
    char data3[128] = "";
    fgets(data3, sizeof(data3), stdin);
    pthread_create(&tid3, NULL, my_func3, data3);

    // 回收线程资源
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);

    // 销毁信号量
    sem_destroy(&sem);

    return 0;
}

输出结果:

案例:sem_synchronous.c

 信号量用于多线程的同步(几个任务就定义几个信号量)

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

// 1、定义三个信号量
sem_t sem1;
sem_t sem2;
sem_t sem3;
void printf_str(char *str)
{
    while (*str)
    {
        printf("%c", *str++);
        fflush(stdout);
        sleep(1);
    }
    printf("\n");
}

void *my_func1(void *arg)
{
    // P -1
    sem_wait(&sem1);
    printf_str((char *)arg);
    // V +1
    sem_post(&sem2);
}
void *my_func2(void *arg)
{
    // P -1
    sem_wait(&sem2);
    printf_str((char *)arg);
    // V +1
    sem_post(&sem3);
}
void *my_func3(void *arg)
{
    // P -1
    sem_wait(&sem3);
    printf_str((char *)arg);
    // V +1
    sem_post(&sem1);
}
int main(int argc, char const *argv[])
{
    // 初始化信号量
    sem_init(&sem1, 0, 1);
    sem_init(&sem2, 0, 0);
    sem_init(&sem3, 0, 0);

    // 2、创建线程
    // 从终端获取数据
    char data[128] = "";
    printf("请输入数据:");
    fgets(data, sizeof(data), stdin);
    pthread_t tid1, tid2, tid3;
    pthread_create(&tid1, NULL, my_func1, data);
    pthread_create(&tid2, NULL, my_func2, "hello");
    pthread_create(&tid3, NULL, my_func3, "world");

    // 回收线程资源
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);

    // 销毁信号量
    sem_destroy(&sem1);
    sem_destroy(&sem2);
    sem_destroy(&sem3);

    return 0;
}

输出结果:每隔一秒打印一个字节(注意汉字是2个或4个字节)

4.1 无名信号量

无名信号量简单来说就是没有名字,通过指针来保存mmap函数返回值,然后通过信号量的初始化函数对无名信号量的指针初始化。(指针可以被初始化)。

无名信号量常用于父子进程间的同步与互斥

 //MAP_ANONYMOUS匿名映射   -1不需要文件描述符
 sem_t *sem1 = mmap(NULL, 
                    sizeof(sem_t), 
                    PROT_READ|PROT_WRITE, 
                    MAP_SHARED|MAP_ANONYMOUS,
                    -1, 
                     0);


//和磁盘映射的函数类似,只是多了一个宏MAP_ANONYMOUS(这个宏就是匿名映射的意思)
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
参数:
    addr 地址,填 NULL
    length 长度 要申请的映射区的长度
    prot 权限
        PROT_READ 可读
        PROT_WRITE 可写
flags 标志位
    MAP_SHARED 共享的 -- 对映射区的修改会影响源文件
    MAP_PRIVATE 私有的
fd 文件描述符 需要打开一个文件
offset 指定一个偏移位置 ,从该位置开始映射
返回值
    成功 返回映射区的首地址
    失败 返回 MAP_FAILED ((void *) -1)

案例:no_name_sem.c(无名信号量用于父子进程的互斥)

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>

void printf_str(char *str)
{
    while (*str)
    {
        printf("%c", *str++);
        fflush(stdout);
        sleep(1);
    }
    printf("\n");
    return;
}

int main(int argc, char const *argv[])
{
    // 定义一个无名信号量
    sem_t *sem = mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);

    // 初始化无名信号量
    sem_init(sem, 1, 1);

    // 从终端获取数据
    char data[128] = "";
    printf("请输入数据:");
    fgets(data, sizeof(data), stdin);
    pid_t pid = fork();
    if (pid == 0) // 子进程
    {
        // P -1
        sem_wait(sem);
        printf_str(data);
        // V +1
        sem_post(sem);
        _exit(-1); // 结束子进程(不会清理资源)
    }
    else
    {
        // P -1
        sem_wait(sem);
        printf_str("world");
        // V +1
        sem_post(sem);
        wait(NULL); // 回收子进程资源
    }

    // 销毁信号量
    sem_destroy(sem);

    return 0;
}

输出结果:

案例:no_name_sem1.c( 无名信号量用于父子进程的同步)

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>

void printf_str(char *str)
{
    while (*str)
    {
        printf("%c", *str++);
        fflush(stdout);
        sleep(1);
    }
    printf("\n");
    return;
}

int main(int argc, char const *argv[])
{
    // 定义两个无名信号量
    sem_t *sem0 = mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    sem_t *sem1 = mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);

    // 初始化无名信号量
    sem_init(sem0, 1, 1);
    sem_init(sem1, 1, 0);

    // 从终端获取数据
    char data[128] = "";
    printf("请输入数据:");
    fgets(data, sizeof(data), stdin);
    pid_t pid = fork();
    if (pid == 0) // 子进程
    {
        // P -1
        sem_wait(sem0);
        printf_str(data);
        // V +1
        sem_post(sem1);
        _exit(-1); // 结束子进程(不会清理资源)
    }
    else
    {
        // P -1
        sem_wait(sem1);
        printf_str("world 你好");
        // V +1
        sem_post(sem0);
        wait(NULL); // 回收子进程资源
    }

    // 销毁信号量
    sem_destroy(sem0);
    sem_destroy(sem1);

    return 0;
}

输出结果:子进程信号量初始值为1(最大),因此他先遍历

 4.2 有名信号量

有名信号量简单来说就是有名字,他常用于不同的进程之间的互斥与同步

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>
Link with -pthread.
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,
mode_t mode, unsigned int value);
参数1:name 代表的是有名信号量的名字
参数2:oflag
    O_RDONLY 只读
    O_WRONLY 只写
    O_RDWR  可读可写
     O_CREAT 不存在创建
     O_EXCL  存在报错
参数3:mode 指的是磁盘权限 0666
参数4:value信号量的初始值

关闭信号量函数

#include <semaphore.h>
int sem_close(sem_t *sem);

Link with -pthread.

删除信号量(实际山是与路径上的文件断开连接)

#include <unistd.h>
int unlink(const char *path);

案例:有名信号量用于不相关进程的互斥

1、name_sem.c

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

void printf_str(char *str)
{
    while (*str)
    {
        printf("%c", *str++);
        fflush(stdout);
        sleep(1);
    }
    printf("\n");
    return;
}
int main(int argc, char const *argv[])
{
    // 1、创建有名信号量
    sem_t *sem = sem_open("sem", O_RDWR | O_CREAT, 0666, 1);

    // P -1
    sem_wait(sem);
    printf_str("world 你好");
    // V +1
    sem_post(sem);
    // 关闭信号量
    sem_close(sem);
    // 销毁信号量
    sem_destroy(sem);

    return 0;
}

2、name1_sem.c

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

void printf_str(char *str)
{
    while (*str)
    {
        printf("%c", *str++);
        fflush(stdout);
        sleep(1);
    }
    printf("\n");
    return;
}
int main(int argc, char const *argv[])
{
    // 1、创建有名信号量
    sem_t *sem = sem_open("sem", O_RDWR | O_CREAT, 0666, 1);

    // P -1
    sem_wait(sem);
    printf_str("hello 欢迎");
    // V +1
    sem_post(sem);
    // 关闭信号量
    sem_close(sem);
    // 销毁信号量
    sem_destroy(sem);

    return 0;
}

输出结果:有一个进程在打印,另一个进程就会被阻塞直到该进程结束任务

案例:有名信号量用于不相关进程的同步

1、name_synchronous.c

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>    /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
void printf_string(char *name)
{
    while (*name)
    {
        printf("%c", *name++);
        fflush(stdout);
        sleep(1); // 便于终端查看
    }
    printf("\n");
    return;
}
int main(int argc, char const *argv[])
{

    // 1、创建一个有名信号量
    sem_t *sem1 = sem_open("sem1", O_RDWR | O_CREAT, 0666, 1);
    sem_t *sem2 = sem_open("sem2", O_RDWR | O_CREAT, 0666, 0);

    // P -1
    sem_wait(sem1);

    printf_string("hello world");

    // v +1
    sem_post(sem2);

    // 2、关闭信号量
    sem_close(sem1);
    sem_close(sem2);

    // 3、销毁信号量
    sem_destroy(sem1);
    sem_destroy(sem2);
    return 0;
}

2、name_synchronous1.c

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>    /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
void printf_string(char *name)
{
    while (*name)
    {
        printf("%c", *name++);
        fflush(stdout);
        sleep(1); // 便于终端查看
    }
    printf("\n");
    return;
}
int main(int argc, char const *argv[])
{

    // 1、创建一个有名信号量
    sem_t *sem1 = sem_open("sem1", O_RDWR | O_CREAT, 0666, 1);
    sem_t *sem2 = sem_open("sem2", O_RDWR | O_CREAT, 0666, 0);

    // P -1
    sem_wait(sem2);

    printf_string("你好,世界");

    // v +1
    sem_post(sem1);

    // 2、关闭信号量
    sem_close(sem1);
    sem_close(sem2);

    // 3、销毁信号量
    sem_destroy(sem1);
    sem_destroy(sem2);
    return 0;
}

输出结果:

 

 Real Time-Thread,实时多线程操作系统,就是一个把任务交给线程来完成的OS

RT-Thread 文档中心https://www.rt-thread.org/document/site/#/

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值