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

目录

同步互斥的概念

互斥锁

初始化互斥锁

销毁互斥锁

申请上锁

解锁

案例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;
}

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

竹烟淮雨

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

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

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

打赏作者

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

抵扣说明:

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

余额充值