线程间通信 | 避免资源竞争、实现同步通信

一、实现方式

1.一个进程空间内部的所有线程共享数据段和堆区,所以全局变量、静态变量,堆区空间都是共享的,可利用这些空间通信;

2.多线程操作全局变量空间时会引入资源竞争;

3.多线程可通过加互斥锁避免资源竞争问题;

4.线程间通信最简单的方法:全局变量+锁

二、原子操作

不会被CPU任务调度打断的一次最小的操作

三、互斥锁

避免多线程资源竞争,配合资源使用,使用资源前加锁,使用资源结束后解锁;

加锁后,无法再次加锁,必须等到解锁后才能继续加锁。

加锁不能阻止CPU任务调度,只是避免资源竞争问题 ,后一个线程想要执行,需等到前一个线程解锁之后才有可能执行

四、临界代码与临界区

加锁解锁中间的代码称为临界代码或临界区;

临界代码和临界区不可能同时被CPU执行。

五、互斥锁函数接口

1.pthread_mutex_init

int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);

功能:互斥锁初始化(在线程创建前初始化)

参数:

mutex:互斥锁空间首地址;

attr:互斥锁属性默认传NULL

返回值:

成功返回0;

失败返回非0

2.pthread_mutex_destory

int pthread_mutex_destory(pthread_mutex_t *restrict mutex);

功能:互斥锁销毁(在线程结束后销毁)

参数:

mutex:互斥锁空间首地址;

返回值:

成功返回0;

失败返回非0

3.pthread_mutex_lock

int pthread_mutex_lock(pthread_mutex_t *mutex);

功能:互斥锁加锁

参数:

mutex:互斥锁空间首地址;

返回值:

成功返回0;

失败返回非0

4.pthread_mutex_unlock

int pthread_mutex_unlock(pthread_mutex_t *mutex);

功能:互斥锁解锁

参数:

mutex:互斥锁空间首地址;

返回值:

成功返回0;

失败返回非0

六、死锁

多任务通信过程中由于加锁导致多个任务均无法向下执行的状态

1.死锁产生的四个必要条件
  • 互斥条件
  • 不可剥夺条件
  • 请求保持
  • 循环等待
2.解决死锁
  • 用pthread_mutex_trylock替代pthread_mutex_lock,如果无法加锁成功完成异常处理流程,防止程序卡死
  • 加锁顺序保持一致

示例:

cpp 复制代码
#include<stdio.h>
#include<pthread.h>

int Num = 100;
int TmpValue1 = 0;
int TmpValue2 = 0;
pthread_mutex_t lock;

void *ThreadFun1(void *arg)
{
    while(1)
    {
        pthread_mutex_lock(&lock);
        TmpValue1 = Num;
        TmpValue2 = Num;
        Num++;
        pthread_mutex_unlock(&lock);
    }

    return NULL;
}

void *ThreadFun2(void *arg)
{
    while(1)
    {
        pthread_mutex_lock(&lock);
        if(TmpValue1 != TmpValue2)
        {
            printf("TmpValue1 = %d TmpValue2 = %d\n", TmpValue1, TmpValue2);
        }
        pthread_mutex_unlock(&lock);
    }
    return NULL;
}

int main(void)
{
    pthread_t tid1;
    pthread_t tid2;

    pthread_mutex_init(&lock, NULL);

    pthread_create(&tid1, NULL, ThreadFun1, NULL);
    pthread_create(&tid2, NULL, ThreadFun2, NULL);

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

    pthread_mutex_destroy(&lock);

    return 0;
}

七、信号量

1.信号量可实现多线程间的同步,让多个任务具有先后顺序关系

同步:具有严格的先后执行的逻辑关系;

异步:代码执行流程没有任何关联性。

2.信号量是一个资源,资源可以初始化、销毁、申请和释放

如果资源数>0,则申请资源是让资源数-1;

如果资源数为0,申请资源时则会阻塞等待,等待有人释放资源,释放资源不会阻塞,会让资源数+1.

八、信息量的函数接口

1.sem_init

int sem_init(sem_t *sem, int pshared, unsigned int value);

功能:对信号量初始化;

参数:

sem:信号量空间首地址;

pshared:信号量作用域;

value:信号量的初始值

返回值:

成功返回0;

失败返回非0

2.sem_destory

int sem_destory(sem_t *sem);

功能:销毁信号量;

参数:

sem:信号量空间首地址;

返回值:

成功返回0;

失败返回非0

3.sem_wait

int sem_wait(sem_t *sem);

功能:申请资源,让资源数-1,如果资源数为0,则阻塞等待

参数:

sem:信号量空间首地址;

返回值:

成功返回0;

失败返回非0

4.sem_post

int sem_post(sem_t *sem);

功能:释放资源,让资源数+1

参数:

sem:信号量空间首地址;

返回值:

成功返回0;

失败返回非0

示例:创建三个线程,线程1循环打印A,线程2循环打印B,线程3循环打印C,按顺序打印出ABC

cpp 复制代码
#include<stdio.h>
#include<pthread.h>
#include<semaphore.h>

sem_t sem1_w;
sem_t sem2_w;
sem_t sem3_w;

void *ThreadFun1(void *arg)
{
    while(1)
    {
        sem_wait(&sem1_w);
        printf("A");
        sem_post(&sem2_w);
    }

    return NULL;
}

void *ThreadFun2(void *arg)
{
    while(1)
    {
        sem_wait(&sem2_w);
        printf("B");
        sem_post(&sem3_w);
    }

    return NULL;
}

void *ThreadFun3(void *arg)
{
    while(1)
    {
        sem_wait(&sem3_w);
        printf("C\n");
        sem_post(&sem1_w);
    }

    return NULL;
}

int main(void)
{
    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;

    sem_init(&sem1_w, 0, 1);
    sem_init(&sem2_w, 0, 0);
    sem_init(&sem3_w, 0, 0);

    pthread_create(&tid1, NULL, ThreadFun1, NULL);
    pthread_create(&tid2, NULL, ThreadFun2, NULL);
    pthread_create(&tid3, NULL, ThreadFun3, NULL);

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

    sem_destroy(&sem1_w);
    sem_destroy(&sem2_w);
    sem_destroy(&sem3_w);

    return 0;
}
相关推荐
Phantom Void9 小时前
服务器处理客户端请求的设计方法
linux·运维·网络
一段路9 小时前
【虚拟机】Linux常用命令
linux·vim
世辰辰辰9 小时前
批量修改图片/文本名子
开发语言·python·批量修改文件名
daad77710 小时前
继续记录无人机SITL的起飞
linux
剑神一笑10 小时前
Linux ls 命令深度解析:从目录遍历到颜色输出的实现原理
linux·服务器·数据库
z落落11 小时前
C# 四种特殊类:抽象类、密封类、静态类、部分类
开发语言·c#
VidDown11 小时前
Webhook 调试器:让第三方回调“原形毕露”
java·开发语言·javascript·编辑器·postman
装不满的克莱因瓶12 小时前
基于 OpenResty 扩展开发实现动态服务注册与发现能力
java·开发语言·架构·openresty
weixin_5231853212 小时前
Java基础知识总结(四):引用数据类型与参数传递机制
java·开发语言·python
Nayxxu12 小时前
Claude API 生产稳定性设计:超时、降级、备用模型和告警怎么做
开发语言·php