线程和进程同步互斥你真的掌握了吗?(同步互斥机制保姆级讲解与应用)

news2024/11/24 8:34:51

目录

同步互斥的概念

互斥锁

初始化互斥锁

销毁互斥锁

申请上锁

解锁

案例1:没有互斥锁 多任务的运行情况

 案例2:有互斥锁 多任务的运行情况

死锁

读写锁

初始化读写锁

销毁读写锁

申请读锁

申请写锁

释放读写锁

案例:两个任务读 一个任务写

条件变量(重要)

概念引入

概念原理

 条件变量初始化

释放条件变量

等待条件

唤醒等待在条件变量上的线程

案例:生产者和消费者

信号量

信号量的API

初始化信号量

信号量减一 P操作

信号量加一 V操作

销毁信号量

使用场景

信号量用于线程的互斥

 信号量用于线程的同步

无名信号量 用于 血缘关系的进程间互斥

无名信号量 用于 血缘关系的进程间同步

有名信号量 用于 无血缘的进程间互斥

创建一个有名信号量

信号量的关闭

信号量文件的删除

案例:完成互斥

有名信号量 用于 无血缘的进程间同步


同步互斥的概念

互斥:同一时间,只能一个任务(进程或线程)执行,谁先运行不确定。

同步:同一时间,只能一个任务(进程或线程)执行,有顺序的运行。

同步 是特殊的 互斥。

互斥锁

用于线程的互斥。

互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即加锁( lock )和解锁( unlock )

互斥锁的操作流程如下:

1)在访问共享资源临界区域前,对互斥锁进行加锁

2)在访问完成后释放互斥锁上的锁。 (解锁)

3)对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到锁 被释放。

互斥锁的数据类型是: pthread_mutex_t

初始化互斥锁

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

功能:

        初始化一个互斥锁。

参数:

        mutex:互斥锁地址。类型是 pthread_mutex_t 。

        attr:设置互斥量的属性,通常可采用默认属性,即可将 attr 设为 NULL。

        可以使用宏 PTHREAD_MUTEX_INITIALIZER 静态初始化互斥锁,比如:

        pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

        这种方法等价于使用 NULL 指定的 attr 参数调用 pthread_mutex_init() 来完成动态初始

化,不同之处在于 PTHREAD_MUTEX_INITIALIZER 宏不进行错误检查

返回值:

        成功:0,成功申请的锁默认是打开的。

        失败:非 0 错误码

销毁互斥锁

#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);

功能:

        销毁指定的一个互斥锁。互斥锁在使用完毕后,必须要对互斥锁进行销毁,以释放资 源。

参数:

        mutex:互斥锁地址。

返回值:

        成功:0

        失败:非 0 错误码

申请上锁

#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错误码

案例1:没有互斥锁 多任务的运行情况

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

void *deal_fun01(void *arg)
{
    char *str = (char*)arg;
    while(*str!='\0')
    {
        printf("%c",*str++);
        //没有换行符,需要强制刷新
        fflush(stdout);
        usleep(500000);
    }
    return NULL;
}

void *deal_fun02(void *arg)
{
    char *str = (char*)arg;
    while(*str!='\0')
    {
        printf("%c",*str++);
        //没有换行符,需要强制刷新
        fflush(stdout);
        usleep(500000);
    }
    return NULL;
}

int main(int argc, char const *argv[])
{
    //创建两个线程
    pthread_t tid1,tid2;

    pthread_create(&tid1,NULL,deal_fun01,"hello");
    pthread_create(&tid2,NULL,deal_fun02,"world");

    pthread_detach(tid1);
    pthread_detach(tid2);

    return 0;
}

由于使用了pthread_detach,创建的线程还没来得及执行,进程就结束了,所以导致输出结果不全

使用pthread_join进行阻塞等待线程结束

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

void *deal_fun01(void *arg)
{
    char *str = (char*)arg;
    while(*str!='\0')
    {
        printf("%c",*str++);
        //没有换行符,需要强制刷新
        fflush(stdout);
        usleep(500000);
    }
    return NULL;
}

void *deal_fun02(void *arg)
{
    char *str = (char*)arg;
    while(*str!='\0')
    {
        printf("%c",*str++);
        //没有换行符,需要强制刷新
        fflush(stdout);
        usleep(500000);
    }
    return NULL;
}

int main(int argc, char const *argv[])
{
    //创建两个线程
    pthread_t tid1,tid2;

    pthread_create(&tid1,NULL,deal_fun01,"hello");
    pthread_create(&tid2,NULL,deal_fun02,"world");

    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);

    return 0;
}

 案例2:有互斥锁 多任务的运行情况

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

//定义一把锁
pthread_mutex_t mutex;
void *deal_fun01(void *arg)
{
    char *str = (char*)arg;

    //上锁
    pthread_mutex_lock(&mutex);
    while(*str!='\0')
    {
        printf("%c",*str++);
        //没有换行符,需要强制刷新
        fflush(stdout);
        usleep(500000);
    }
    //解锁
    pthread_mutex_unlock(&mutex);
    return NULL;
}

void *deal_fun02(void *arg)
{
    char *str = (char*)arg;
    //上锁
    pthread_mutex_lock(&mutex);
    while(*str!='\0')
    {
        printf("%c",*str++);
        //没有换行符,需要强制刷新
        fflush(stdout);
        usleep(500000);
    }
    //解锁
    pthread_mutex_unlock(&mutex);
    return NULL;
}

int main(int argc, char const *argv[])
{
    //初始化锁
    pthread_mutex_init(&mutex,NULL);

    //创建两个线程
    pthread_t tid1,tid2;

    pthread_create(&tid1,NULL,deal_fun01,"hello");
    pthread_create(&tid2,NULL,deal_fun02,"world");

    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    //销毁锁
    pthread_mutex_destroy(&mutex);
    return 0;
}

 由于线程是相互独立的,所以线程处理函数可以合成一个函数

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

//定义一把锁
pthread_mutex_t mutex;
void *deal_fun(void *arg)
{
    char *str = (char*)arg;

    //上锁
    pthread_mutex_lock(&mutex);
    while(*str!='\0')
    {
        printf("%c",*str++);
        //没有换行符,需要强制刷新
        fflush(stdout);
        usleep(500000);
    }
    //解锁
    pthread_mutex_unlock(&mutex);
    return NULL;
}


int main(int argc, char const *argv[])
{
    //初始化锁
    pthread_mutex_init(&mutex,NULL);

    //创建两个线程
    pthread_t tid1,tid2;

    pthread_create(&tid1,NULL,deal_fun,"hello");
    pthread_create(&tid2,NULL,deal_fun,"world");

    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    //销毁锁
    pthread_mutex_destroy(&mutex);
    return 0;
}

总结:

如果是互斥 不管有多少个任务,只需要一把锁,所有的任务上锁 访问资源 解锁

死锁

读写锁

POSIX 定义的读写锁的数据类型是: pthread_rwlock_t

初始化读写锁

#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *rwlock,const pthread_rwlockattr_t *attr);

功能:

        用来初始化 rwlock 所指向的读写锁。

参数:

        rwlock:指向要初始化的读写锁指针。

        attr:读写锁的属性指针。如果 attr 为 NULL 则会使用默认的属性初始化读写锁,否则 使用指定的 attr 初始化读写锁。

        可以使用宏 PTHREAD_RWLOCK_INITIALIZER 静态初始化读写锁,比如:

        pthread_rwlock_t my_rwlock = PTHREAD_RWLOCK_INITIALIZER;

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

返回值:

        成功:0,读写锁的状态将成为已初始化和已解锁。

        失败:非 0 错误码。

销毁读写锁

#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

功能:

        用于销毁一个读写锁,并释放所有相关联的资源(所谓的所有指的是由 pthread_rwlock_init() 自动申请的资源) 。

参数:

        rwlock:读写锁指针。

返回值:

        成功:0

        失败:非 0 错误码

申请读锁

#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);

用于尝试以非阻塞的方式来在读写锁上获取写锁。

如果有任何的读者或写者持有该锁,则立即失败返回。

释放读写锁

#include <pthread.h>
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

功能:

        无论是读锁或写锁,都可以通过此函数解锁。

参数:

        rwlock:读写锁指针。

返回值:

        成功:0

        失败:非 0 错误码

案例:两个任务读 一个任务写

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

//创建一把读写锁
pthread_rwlock_t rwlock;

void *read_data01(void *arg)
{
    int *p=(int*)arg;
    while(1)
    {
        //申请上读锁
        pthread_rwlock_rdlock(&rwlock);

        printf("任务A:num=%d\n",*p);
        //解读写锁
        pthread_rwlock_unlock(&rwlock);
        sleep(1);
    }
    return NULL;
}

void *read_data02(void *arg)
{
    int *p=(int*)arg;
    while(1)
    {
        //申请上读锁
        pthread_rwlock_rdlock(&rwlock);

        printf("任务B:num=%d\n",*p);
        //解读写锁
        pthread_rwlock_unlock(&rwlock);
        sleep(1);
    }
    return NULL;
}

void *write_data(void *arg)
{
    int *p=(int*)arg;
    while(1){
        //申请上写锁
        pthread_rwlock_wrlock(&rwlock);
        (*p)++;

        //解读写锁
        pthread_rwlock_unlock(&rwlock);
        printf("任务C:写入num=%d\n",*p);
        sleep(2);
    }
    return NULL;
}

int main(int argc, char const *argv[])
{
    //定义一个公共资源
    int num=0;

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

    pthread_t tid1,tid2,tid3;
    pthread_create(&tid1,NULL,read_data01,&num);
    pthread_create(&tid2,NULL,read_data02,&num);
    pthread_create(&tid3,NULL,write_data,&num);

    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    pthread_join(tid3,NULL);

    //销毁锁
    pthread_rwlock_destroy(&rwlock);
    return 0;
}

 

条件变量(重要)

概念引入

对于刚刚死锁的第三种情况。如果任务A先执行上锁,由于管道没有数据,所以读不到数据会一直阻塞,导致无法解锁;任务A无法解锁就会导致任务B无法上锁,也就无法向管道写入数据,同样会导致任务B阻塞等待上锁。

我们按照进程管道通信的方法,可以使用非阻塞读取来解决这个死锁问题。

但我们还可以通过条件变量来进行解决,任务A在执行到读数据的时候,发现管道里没有数据,就会通过条件变量临时解锁,从而通知任务B成功上锁进行数据写入。

概念原理

条件变量是用来等待条件满足而不是用来上锁的,条件变量本身不是锁。

条件变量和互斥锁同时使用。

条件变量的两个动作: 条件不满, 阻塞线程。当条件满足, 通知阻塞的线程开始工作。

条件变量的类型: pthread_cond_t。

原子操作: 几个操作连续进行,不可分割,不能被其它代码操作插入和打断。

 条件变量初始化

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cond,
const pthread_condattr_t *attr);

功能:

        初始化一个条件变量

参数:

        cond:指向要初始化的条件变量指针。

        attr:条件变量属性,通常为默认值,传NULL即可 。

也可以使用静态初始化的方法,初始化条件变量:

        pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

返回值:

        成功:0

        失败:非0错误号

释放条件变量

#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);

功能:

        销毁一个条件变量

参数:

        cond:指向要初始化的条件变量指针

返回值:

        成功:0

        失败:非0错误号

等待条件

#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cond,
pthread_mutex_t *mutex);

功能:

        阻塞等待一个条件变量

        如果阻塞就 先解锁、等待条件满足、重新上锁(3步为原子操作)解阻塞

参数:

        cond:指向要初始化的条件变量指针

        mutex:互斥锁

返回值:

        成功:0

        失败:非0错误号

int pthread_cond_timedwait(pthread_cond_t *cond,
            pthread_mutex_t *mutex,const struct *abstime);

功能:

        限时等待一个条件变量

参数:

        cond:指向要初始化的条件变量指针

        mutex:互斥锁

        abstime:绝对时间

返回值:

        成功:0

        失败:非0错误号

唤醒等待在条件变量上的线程

#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错误号

案例:生产者和消费者

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
//定义互斥锁
pthread_mutex_t mutex;
//定义条件变量
pthread_cond_t cond;
//定义一个仓库 默认有三个产品
int num = 3;

void *comsumption_fun(void *arg) //消费
{
    while (1)
    {
        //申请上锁
        pthread_mutex_lock(&mutex);

        //判断仓库是否为空 等待条件变量满足
        if (0 == num) //仓库为空
        {
            printf("%s 发现仓库为空,等待生产\n", (char *)arg);
            pthread_cond_wait(&cond, &mutex);
        }
        else
        {
            //进入仓库购买产品
            num--;
            printf("%s 购买了一个产品,仓库剩余%d个\n", (char *)arg, num);
            //使用产品
            printf("%s 正在使用产品\n", (char *)arg);
        }

        //解锁
        pthread_mutex_unlock(&mutex);

        sleep(rand() % 5);
    }
}

void *production_fun(void *arg) //生产
{
    while (1)
    {
        //生产一个产品
        sleep(rand() % 5);

        //上锁 进入仓库
        pthread_mutex_lock(&mutex);

        //将产品放入仓库
        num++;
        printf("%s 放入一个产品,仓库剩余%d\n", (char *)arg, num);

        //通知 条件变量阻塞的线程
        pthread_cond_broadcast(&cond);

        //解锁
        pthread_mutex_unlock(&mutex);
    }
}
int main(int argc, char const *argv[])
{
    //设计随机数种子
    srand(time(NULL));
    //初始化锁
    pthread_mutex_init(&mutex, NULL);

    //初始化条件变量
    pthread_cond_init(&cond, NULL);

    pthread_t tid1, tid2, tid3;

    pthread_create(&tid1, NULL, comsumption_fun, "消费者A");
    pthread_create(&tid2, NULL, comsumption_fun, "消费者B");
    pthread_create(&tid3, NULL, production_fun, "生产者");

    //等待线程结束
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);

    //销毁锁
    pthread_mutex_destroy(&mutex);
    //销毁条件变量
    pthread_cond_destroy(&cond);

    return 0;
}

信号量

信号量也叫信号灯,其本质就是一个计数器,描述临界资源的数目大小。(最多能有多少资源分配给线程)。

信号量可参考:信号量的概念 及其 操作函数(有名、无名信号量)_仲夏夜之梦~的博客-CSDN博客

前面的锁和条件变量只用于线程间同步互斥。而信号量广泛用于进程线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被 用来控制对公共资源的访问。

适用于六种情况:线程间的同步与互斥、有血缘进程间的同步与互斥、无血缘进程间的同步与互斥。

当信号量值大于 0 时,则可以访问,否则将阻塞

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

号量数据类型为:sem_t

信号量用于互斥

不管多少个任务互斥 只需要一个信号量(与前面的锁机制相同)。先P 任务 再 V

 

信号量用于同步

有多少个任务 就需要多少个信号量。最先执行的任务对应的信号量为1,其他信号量全 部为0。

每任务先P自己 任务 再V下一个任务的信号量

 

信号量的API

初始化信号量

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value)

功能:

        创建一个信号量并初始化它的值。一个无名信号量在被使用前必须先初始化。

参数:

        sem:信号量的地址

        pshared:等于 0,信号量在线程间共享(常用);不等于0,信号量在进程间共享。

        value:信号量的初始值

返回值:

        成功:0

        失败: - 1

信号量减一 P操作

int sem_wait(sem_t *sem);

功能: 将信号量减一,如果信号量的值为0 则阻塞,大于0可以减一

参数:信号量的地址

返回值:成功返回0 失败返回-1

尝试对信号量减一

int sem_trywait(sem_t *sem);

功能: 尝试将信号量减一,如果信号量的值为0 不阻塞,立即返回 ,大于0可以减一

参数:信号量的地址

返回值:成功返回0 失败返回-1

信号量加一 V操作

int sem_post(sem_t *sem);

功能:将信号量加一

参数:信号量的地址

返回值:成功返回0 失败返回-1

销毁信号量

int sem_destroy(sem_t *sem);

功能: 销毁信号量

参数: 信号量的地址

返回值:成功返回0 失败返回-1

使用场景

信号量用于线程互斥

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

//定义一个信号量(用于互斥)
sem_t sem;

void my_print(char* str)
{
    int i=0;
    while(str[i]!='\0')
    {
        printf("%c",str[i++]);
        fflush(stdout);
        sleep(1);
    }
    return;
}

void *task_fun(void *arg)
{
    //P操作
    sem_wait(&sem);

    my_print((char*)arg);

    //V操作
    sem_post(&sem);
    return NULL;
}

int main(int argc, char const *argv[])
{
    //信号量初始化为1
    sem_init(&sem,0,1);

    pthread_t tid1,tid2,tid3;
    pthread_create(&tid1,NULL,task_fun,"hello");
    pthread_create(&tid2,NULL,task_fun,"world");
    pthread_create(&tid3,NULL,task_fun,"nanjing");
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    pthread_join(tid3,NULL);
    
    //销毁信号量
    sem_destroy(&sem);
    
    return 0;
}

 信号量用于线程同步

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

//定义一个信号量(用于互斥)
sem_t sem1,sem2,sem3;

void my_print(char* str)
{
    int i=0;
    while(str[i]!='\0')
    {
        printf("%c",str[i++]);
        fflush(stdout);
        sleep(1);
    }
    return;
}

void *task_fun01(void *arg)
{
    //P操作
    sem_wait(&sem2);

    my_print((char*)arg);

    //V操作
    sem_post(&sem3);
    return NULL;
}

void *task_fun02(void *arg)
{
    //P操作
    sem_wait(&sem3);

    my_print((char*)arg);

    //V操作
    sem_post(&sem1);
    return NULL;
}

void *task_fun03(void *arg)
{
    //P操作
    sem_wait(&sem1);

    my_print((char*)arg);

    //V操作
    sem_post(&sem2);
    return NULL;
}

int main(int argc, char const *argv[])
{
    //信号量初始化为1
    sem_init(&sem1,0,1);
    sem_init(&sem2,0,0);
    sem_init(&sem3,0,0);

    pthread_t tid1,tid2,tid3;
    pthread_create(&tid1,NULL,task_fun01,"hello");
    pthread_create(&tid2,NULL,task_fun02,"world");
    pthread_create(&tid3,NULL,task_fun03,"nanjing");
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    pthread_join(tid3,NULL);

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

无名信号量 用于 血缘关系进程间互斥

如下图所示,通过一个普通的信号量想要实现父子进程间的互斥,由于父子进程使用的是独立的代码空间,因此当fork后,两个进程都会执行PV操作,因此这种方法无法实现进程互斥。

我们可以保证信号量是父子进程公共识别的,让信号量脱离父子进程空间。我们可以通过磁盘映射、存储映射、内存共享

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

void my_print(char *str)
{
    int i = 0;
    while (str[i] != '\0')
    {
        printf("%c", str[i++]);
        fflush(stdout);
        sleep(1);
    }
    return;
}

int main(int argc, char const *argv[])
{
    //定义一个无名信号量
    // MAP_ANONYMOUS匿名映射(不使用文件描述符),fd为-1表示不需要文件描述符
    sem_t *sem = mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);

    //无名信号量的初始化    参数2:1表示进程
    sem_init(sem, 1, 1);

    pid_t pid = fork();
    if (pid == 0) //子进程
    {
        // P操作
        sem_wait(sem);

        my_print("hello");

        // V操作
        sem_post(sem);
    }
    else if (pid > 0) //父进程
    {
        // P操作
        sem_wait(sem);

        my_print("world");

        // V操作
        sem_post(sem);

        int status = 0;
        pid_t pid = wait(&status);
    }

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

无名信号量 用于 血缘关系进程间同步

进程间的同步需要用到两个信号量

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

void my_print(char *str)
{
    int i = 0;
    while (str[i] != '\0')
    {
        printf("%c", str[i++]);
        fflush(stdout);
        sleep(1);
    }
    return;
}

int main(int argc, char const *argv[])
{
    //定义一个无名信号量
    // MAP_ANONYMOUS匿名映射(不使用文件描述符),fd为-1表示不需要文件描述符
    sem_t *sem1 = mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    sem_t *sem2 = mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    //无名信号量的初始化    参数2:1表示进程
    sem_init(sem1, 1, 1);
    sem_init(sem2, 1, 0);

    pid_t pid = fork();
    if (pid == 0) //子进程
    {
        // P操作
        sem_wait(sem1);

        my_print("hello");

        // V操作
        sem_post(sem2);
    }
    else if (pid > 0) //父进程
    {
        // P操作
        sem_wait(sem2);

        my_print("world");

        // V操作
        sem_post(sem1);

        int status = 0;
        pid_t pid = wait(&status);
    }

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

有名信号量 用于 无血缘进程间互斥

有名信号量的使用类似于文件操作,有名信号量创建完成之后,当前整个系统有效,直到系统重启或通过sem_unlink函数手动删除。

创建一个有名信号量

创建并初始化有名信号量或打开一个已存在的有名信号量。

注: 如果指定了O_CREAT(而没有指定O_EXCL),那么只有所需的信号量尚未存在时才初始化他。 如果所需的信号量已经存在,mode,value都会被忽略。

#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:sem_open函数的权限标志

        mode:文件权限(可读、可写、可执行 0777)的设置

        value:信号量的初始值

返回值:

        信号量的地址,失败:SEM_FAILED

信号量的关闭
int sem_close(sem_t *sem);

功能:关闭信号量

参数:信号量的的地址

返回值:成功0 失败-1

信号量文件的删除
#include <semaphore.h>
int sem_unlink(const char *name);

功能:删除信号量的文件

参数:信号量的文件名

返回值:成功0 失败-1

案例:完成互斥

TaskA.c

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

void my_print(char *str)
{
    int i = 0;
    while (str[i] != '\0')
    {
        printf("%c", str[i++]);
        fflush(stdout);
        sleep(1);
    }
    return;
}

int main(int argc, char const *argv[])
{
    //创建一个有名信号量sem_open类似文件操作    最后一个参数为初始值
    sem_t *sem = sem_open("sem",O_RDWR|O_CREAT,0666,1);

    //P 操作
    sem_wait(sem);

    //任务
    my_print("hello world");

    //V 操作
    sem_post(sem);

    //关闭信号量
    sem_close(sem);

    //销毁信号量
    sem_destroy(sem);
    return 0;
}

TaskB.c

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

void my_print(char *str)
{
    int i = 0;
    while (str[i] != '\0')
    {
        printf("%c", str[i++]);
        fflush(stdout);
        sleep(1);
    }
    return;
}

int main(int argc, char const *argv[])
{
    //创建一个有名信号量sem_open类似文件操作    最后一个参数为初始值
    sem_t *sem = sem_open("sem",O_RDWR|O_CREAT,0666,1);

    //P 操作
    sem_wait(sem);

    //任务
    my_print("nanjing 8.20");

    //V 操作
    sem_post(sem);

    //关闭信号量
    sem_close(sem);

    //销毁信号量
    sem_destroy(sem);
    return 0;
}

运行效果:先运行A,再运行B。A关闭有名信号量前,B无法打开有名信号量(阻塞)

有名信号量 用于 无血缘进程间同步

TaskA.c

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

void my_print(char *str)
{
    int i = 0;
    while (str[i] != '\0')
    {
        printf("%c", str[i++]);
        fflush(stdout);
        sleep(1);
    }
    return;
}

int main(int argc, char const *argv[])
{
    //创建一个有名信号量sem_open类似文件操作    最后一个参数为初始值
    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 操作
    sem_wait(sem1);

    //任务
    my_print("hello world");

    //V 操作
    sem_post(sem2);

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

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

TaskB.c

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

void my_print(char *str)
{
    int i = 0;
    while (str[i] != '\0')
    {
        printf("%c", str[i++]);
        fflush(stdout);
        sleep(1);
    }
    return;
}

int main(int argc, char const *argv[])
{
    //创建一个有名信号量sem_open类似文件操作    最后一个参数为初始值
    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 操作
    sem_wait(sem2);

    //任务
    my_print("nanjing 8.20");

    //V 操作
    sem_post(sem1);

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

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

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

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

相关文章

回归预测 | MATLAB实现GA-RBF遗传算法优化径向基函数神经网络多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现GA-RBF遗传算法优化径向基函数神经网络多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现GA-RBF遗传算法优化径向基函数神经网络多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09;效果…

从0到1:通用后台管理系统 echarts图使用及其参数

这一章主要讲在系统概览模块中&#xff0c;所使用的echarts图及其参数 echarts是一个基于 JavaScript 的开源可视化图表库&#xff0c; 官网直通车 是在各种后台管理系统的开发中都常见的一种库&#xff0c;也是前端开发管理系统所必学的一种库 那么在项目中主要是使用了饼…

【前端】React快速入门+Redux状态管理

本文旨在记录react的基础内容&#xff0c;帮助有需要的同学快速上手,需要进一步了解描述更加稳妥和全面的信息&#xff0c;请查阅官方文档 官方文档点击这里进行跳转 React快速入门 先导 react框架 vue,react,angular这几种主流前端框架使用频率较高…本质还是js库。 React…

1.Jetson Orin Nano Developer Kit系统刷机

本教程有3种方法刷机&#xff0c;根据需要自己选择适合自己的方案。 一:使用>32G的SD卡安装开发套件; 二:在Ubuntu18.04下通过SDK Manager软件在线安装系统. 三:在Ubuntu18.04下通过脚本方式安装系统. 注意&#xff1a;Ubuntu的账号不能为一些常见的包名如&#xff1a;p…

【学习日记】【FreeRTOS】FreeRTOS 移植到 STM32F103C8

前言 本文基于野火 FreeRTOS 教程&#xff0c;内容是关于 FreeRTOS 官方代码的移植的注意事项&#xff0c;并将野火例程中 STM32F103RC 代码移植到 STM32F103C8。 一、FreeRTOS V9.0.0 源码的获取 两个下载链接&#xff1a; 官 网 代码托管 二、源码文件夹内容简介 Source…

Docker(一)-安装、架构、业务开发常用命令、Dockerile、镜像卷、镜像仓库

基于业务开发使用Docker Docker是一个开源的容器引擎&#xff0c;它有助于更快地交付应用。Docker可将应用程序和基础设施层隔离&#xff0c;并且能将基础设施当作程序一样进行管理。使用 Docker可更快地打包、测试以及部署应用程序&#xff0c;并可以缩短从编写到部署运行代码…

为什么需要单元测试?

为什么需要单元测试&#xff1f; 从产品角度而言&#xff0c;常规的功能测试、系统测试都是站在产品局部或全局功能进行测试&#xff0c;能够很好地与用户的需要相结合&#xff0c;但是缺乏了对产品研发细节&#xff08;特别是代码细节的理解&#xff09;。 从测试人员角度而言…

Springboot 实践(10)spring cloud 与consul配置运用之服务的注册与发现

前文讲解&#xff0c;完成了springboot、spring security、Oauth2.0的继承&#xff0c;实现了对系统资源的安全授权、允许获得授权的用户访问&#xff0c;也就是实现了单一系统的全部技术开发内容。 Springboot是微服务框架&#xff0c;单一系统只能完成指定系统的功能&#xf…

【简单认识Docker网络管理】

文章目录 一、Docker 网络实现原理二、Docker 的网络模式1.四种网络模式2.各网络模式详解&#xff08;1&#xff09;Host模式&#xff08;2&#xff09;Container模式&#xff08;3&#xff09;None模式&#xff08;4&#xff09;Bridge模式 3.指定容器网络模式4.自定义网络模式…

web文件上传

文件上传指的是&#xff0c;将本地的图片、视频、音频上传到服务器&#xff0c;提供给其他用户浏览和下载的过程 前端需求 想要进行文件上传对于web前端来说有三个重要要素 1.<input type"file" name"image"> 提供这样的file文件上传格式 2. metho…

【Unity】自带的录屏插件Recorder

目录 Recorder简介Recorder导入Recorder使用 Recorder简介 Recorder是Unity官方的录屏插件&#xff0c;可以直接录制Game窗口&#xff0c;还可以录制不同相机的视图。不仅可以直接生成视频、帧动画图、还可以制作gif和animation。 Recorder导入 菜单栏Windows→Package Mana…

VINS-Mono中的边缘化与滑窗 (4)——VINS边缘化为何是局部变量边缘化?

文章目录 0.前言1.系统构建1.1.仿真模型1.2.第一次滑窗优化1.3.第二次全局优化 2.边缘化时不同的舒尔补方式2.1.边缘化时舒尔补的意义2.2.不同的边缘化方式 3.边缘化时不同的舒尔补方式实验验证3.1.全局schur的操作方式3.2.VIO或VINS中局部边缘化的方式3.3.两种方式和全局优化方…

【Linux】文件的描述符和重定向

文件的描述符和重定向 C语言的文件读写操作代码 open系统打开文件方法系统读写文件操作文件描述符文件重定向怎么理解文件缓冲区 C语言的文件读写操作 文件写入 fputs int fputs(const char *s, FILE *stream); s&#xff1a;要写入的字符串 stream&#xff1a;要写入对应的目标…

halcon库文件封装操作方法介绍

头文件写法参考 getarea.h 导出函数设定&#xff1a; extern “C” __declspec(dllexport) void _stdcall cpp文件写法参看 3.vs 库文件配置介绍 头文件及包含的库文件目录设定 &#xff08;1&#xff09;头文件设定E:\halcon\env\opencv\include\opencv2;E:\halcon\env\ope…

接口限流注解 RateLimiter (现成直接用)

1. 限流注解 import com.ruoyi.common.constant.Constants; import com.ruoyi.common.enums.LimitType;import java.lang.annotation.*;/*** 限流注解* */ Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) Documented public interface RateLimiter {/*** 限流…

DSO 系列文章(3)——DSO后端正规方程构造与Schur消元

文章目录 DSO代码注释&#xff1a;https://github.com/Cc19245/DSO-CC_Comments

javascript期末作业【三维房屋设计】 【文末源码+文档下载】

1、引入three.js库 官网下载three.js 库 放置目录并引用 引入js文件: 设置场景&#xff08;scene&#xff09; &#xff08;1&#xff09;创建场景对象 &#xff08;2&#xff09;设置透明相机 1,透明相机的优点 透明相机机制更符合于人的视角,在场景预览和游戏场景多有使用…

安装docker配置镜像加速器,容器等

1.安装docker服务&#xff0c;配置镜像加速器 2.下载系统镜像&#xff08;Ubuntu、 centos&#xff09; 3.基于下载的镜像创建两个容器 &#xff08;容器名一个为自己名字全拼&#xff0c;一个为首名字字母&#xff09; 4.容器的启动、 停止及重启操作 5.怎么查看正在运行的容器…

快速排序 | C++|时间空间复杂度

1.概念 快速排序(QuickSort)的基本思想是:通过一趟排序将待排记录分割成独立的两部分&#xff0c;其中一部分记录的关键字均比另一部分记录的关键字小&#xff0c;则可分别对这两部分记录继续进行排序&#xff0c;以达到整个序列有序的目的。 2.算法思想描述 1.进行一次划分&…

在线HmacMd5加密工具--在线获取哈希值又称摘要

具体请前往&#xff1a;在线计算HMacMd5工具