多进程、多线程通讯的方法(二)

写在前面:

        承接上文,提及了进程间的通讯方式,下面介绍的是线程间的通讯方式

线程间通讯:

(1)信号量(2)读写锁(3)互斥锁(4)条件变量(5)自旋锁

(1)信号量

        信号量时在进程和线程间通讯广泛应用的,信号量的本质时一个非负的整数计数器(控制对临界资源的访问)。

  • P 操作(等待):如果信号量的值大于 0,则将信号量的值减 1;如果信号量的值为 0,则当前线程将被阻塞,直到信号量的值大于 0。
  • V 操作(发信号):将信号量的值加 1。
信号量的线程同步:
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

sem_t sem;

void *thread_func1(void *arg)
{
    printf("Thread 1 is waiting...\n");
    sem_wait(&sem); // 等待信号量 sem 变为非零

    printf("Thread 1 starts.\n");
    // 执行任务
    printf("Thread 1 finishes.\n");

    sem_post(&sem); // 发信号,增加信号量 sem 的值
    return NULL;
}

void *thread_func2(void *arg)
{
    printf("Thread 2 starts.\n");
    // 执行任务
    printf("Thread 2 finishes.\n");

    sem_post(&sem); // 发信号,增加信号量 sem 的值
    return NULL;
}

int main()
{
    pthread_t tid1, tid2;

    sem_init(&sem, 0, 0); // 初始化信号量 sem,初始值为 0

    pthread_create(&tid1, NULL, thread_func1, NULL);
    pthread_create(&tid2, NULL, thread_func2, NULL);

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

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

    return 0;
}
信号量的线程互斥:
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

sem_t mutex;
int shared_data = 0;

void *thread_func1(void *arg)
{
    sem_wait(&mutex); // 加锁操作

    // 临界区:访问共享资源 shared_data
    shared_data++;
    printf("Thread 1: shared_data = %d\n", shared_data);

    sem_post(&mutex); // 解锁操作

    return NULL;
}

void *thread_func2(void *arg)
{
    sem_wait(&mutex); // 加锁操作

    // 临界区:访问共享资源 shared_data
    shared_data += 2;
    printf("Thread 2: shared_data = %d\n", shared_data);

    sem_post(&mutex); // 解锁操作

    return NULL;
}

int main()
{
    pthread_t tid1, tid2;

    sem_init(&mutex, 0, 1); // 初始化互斥信号量 mutex,初始值为 1

    pthread_create(&tid1, NULL, thread_func1, NULL);
    pthread_create(&tid2, NULL, thread_func2, NULL);

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

    sem_destroy(&mutex); // 销毁互斥信号量 mutex

    return 0;
}
(2)读写锁

如果3个或3个以上 任务, 多个读,多个写 ----> 建议用读写锁

        读写锁

        一个任务读操作时,其他任务可以读,不能写,一个或多个任务上读锁成功,其他任务可以上读锁,不能上写锁

        一个任务写操作时,其他任务不能读/写,一个任务上写锁成功时,其他任务不能上读锁和写锁

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

int a = 10; // 全局共享变量
pthread_rwlock_t rwlock; // 声明读写锁

// 读线程函数
void *rfunc(void *arg)
{
    while (1)
    {
        // 申请读锁
        pthread_rwlock_rdlock(&rwlock);
        
        // 读资源
        printf("%s a = %d\n", (char *)arg, a);
        
        // 解锁读锁
        pthread_rwlock_unlock(&rwlock);
        
        sleep(1); // 模拟其他操作的延迟
    }
}

// 另一个读线程函数
void *rfunc1(void *arg)
{
    while (1)
    {
        // 申请读锁
        pthread_rwlock_rdlock(&rwlock);
        
        // 读资源
        printf("%s a = %d\n", (char *)arg, a);
        
        // 解锁读锁
        pthread_rwlock_unlock(&rwlock);
        
        sleep(1); // 模拟其他操作的延迟
    }
}

// 写线程函数
void *wfunc(void *arg)
{
    while (1)
    {
        // 申请写锁
        pthread_rwlock_wrlock(&rwlock);
        
        // 写资源
        a++;
        printf("%s a = %d\n", (char *)arg, a);
        
        // 解锁写锁
        pthread_rwlock_unlock(&rwlock);
        
        sleep(1); // 模拟其他操作的延迟
    }
}

int main(int argc, char const *argv[])
{
    // 初始化读写锁
    if (pthread_rwlock_init(&rwlock, NULL) != 0)
    {
        printf("读写锁初始化失败\n");
        return 1;
    }

    // 创建三个线程
    pthread_t lwp1, lwp2, lwp3;

    // 创建读线程1
    if (pthread_create(&lwp1, NULL, rfunc, "线程1 读数据:") != 0)
    {
        printf("创建线程1失败\n");
        return 1;
    }

    // 创建读线程2
    if (pthread_create(&lwp2, NULL, rfunc1, "线程2 读数据:") != 0)
    {
        printf("创建线程2失败\n");
        return 1;
    }

    // 创建写线程
    if (pthread_create(&lwp3, NULL, wfunc, "线程3 写入数据:") != 0)
    {
        printf("创建线程3失败\n");
        return 1;
    }

    // 等待线程结束
    pthread_join(lwp1, NULL);
    pthread_join(lwp2, NULL);
    pthread_join(lwp3, NULL);

    // 销毁读写锁
    pthread_rwlock_destroy(&rwlock);

    return 0;
}

(3)互斥锁

如果只有两个任务,一个读 一个写!! -----> 互斥锁

        互斥锁,只有两种状态:加锁(lock)和解锁(unlock);同一时刻,只能有一个任务上锁,其他任务阻塞(大致就是访问临界资源前上锁,其他任务阻塞,访问结束后解锁,其他任务才能上锁);死锁,是不允许的,出现死锁的情况如下两种

1.上完锁,未解锁                   解决方法:上锁与解锁必须一一对应使用

2.多把锁的上锁顺序问题        解决方法:规定好上锁顺序

互斥锁的操作流程:
在访问共享资源前,对互斥锁进行加锁

在访问共享资源后,对互斥锁进行解锁

对互斥锁进行加锁后,任何试图再次对互斥锁加锁的任务都会阻塞,直到锁被释放

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

// 定义互斥锁
pthread_mutex_t mutx;

// 打印字符串的函数,带有逐字符输出和延时效果
void printf_string(void *str)
{
    char *p = (char *)str;
    int i = 0;
    while (p[i] != '\0')
    {
        printf("%c", p[i++]); // 输出单个字符
        fflush(stdout);       // 立即刷新输出,确保及时显示
        sleep(1);             // 模拟耗时操作
    }
}

// 第一个线程函数
void *func(void *arg)
{
    pthread_mutex_lock(&mutx); // 上锁

    printf_string(arg); // 使用共享资源(标准输出)

    pthread_mutex_unlock(&mutx); // 解锁
}

// 第二个线程函数
void *func1(void *arg)
{
    pthread_mutex_lock(&mutx); // 上锁

    printf_string(arg); // 使用共享资源(标准输出)

    pthread_mutex_unlock(&mutx); // 解锁
}

int main(int argc, char const *argv[])
{
    // 初始化互斥锁
    if (pthread_mutex_init(&mutx, NULL) != 0)
    {
        printf("Mutex initialization failed.\n");
        return 1;
    }

    // 创建两个线程
    pthread_t lwp1, lwp2;
    if (pthread_create(&lwp1, NULL, func, "hello world!\n") != 0)
    {
        printf("Thread creation failed.\n");
        return 1;
    }
    if (pthread_create(&lwp2, NULL, func1, "nihao!\n") != 0)
    {
        printf("Thread creation failed.\n");
        return 1;
    }

    // 等待线程结束
    pthread_join(lwp1, NULL);
    pthread_join(lwp2, NULL);

    // 销毁互斥锁
    pthread_mutex_destroy(&mutx);

    return 0;
}
(4)条件变量

        与互斥锁不一样,条件变量时用来等待的而不是用来上锁的,条件变量本身不是锁。

        条件变量时用来自动阻塞一个线程,之道某特殊情况发生为止。

        通常条件变量和互斥锁同时使用。条件变量的两个动作:1.条件不满,阻塞线程    2.当条件满足,通知阻塞的线程开始工作。

        生产者消费者问题是经典的多线程同步问题,其中生产者线程生成数据并放入共享缓冲区,而消费者线程从缓冲区取出数据进行消费。以下是一个简单的生产者消费者问题示例,使用互斥锁和条件变量来实现线程间的同步与通信。

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

#define BUFFER_SIZE 5 // 缓冲区大小
#define PRODUCE_COUNT 10 // 生产次数和消费次数

int buffer[BUFFER_SIZE]; // 缓冲区
int in = 0; // 生产者放置数据的位置
int out = 0; // 消费者取数据的位置
int count = 0; // 缓冲区中当前数据个数

pthread_mutex_t mutex; // 互斥锁
pthread_cond_t full_cond; // 缓冲区满条件变量
pthread_cond_t empty_cond; // 缓冲区空条件变量

// 生产者线程函数
void *producer(void *arg)
{
    int i;
    for (i = 0; i < PRODUCE_COUNT; ++i)
    {
        pthread_mutex_lock(&mutex); // 加锁

        while (count == BUFFER_SIZE) // 如果缓冲区满,则等待消费者消费
            pthread_cond_wait(&full_cond, &mutex);

        buffer[in] = i; // 生产数据
        printf("生产者生产数据:%d\n", buffer[in]);
        in = (in + 1) % BUFFER_SIZE;
        count++;

        pthread_cond_signal(&empty_cond); // 发送缓冲区非空信号
        pthread_mutex_unlock(&mutex); // 解锁

        sleep(1); // 模拟生产过程
    }
    pthread_exit(NULL);
}

// 消费者线程函数
void *consumer(void *arg)
{
    int i, data;
    for (i = 0; i < PRODUCE_COUNT; ++i)
    {
        pthread_mutex_lock(&mutex); // 加锁

        while (count == 0) // 如果缓冲区空,则等待生产者生产
            pthread_cond_wait(&empty_cond, &mutex);

        data = buffer[out]; // 消费数据
        printf("消费者消费数据:%d\n", data);
        out = (out + 1) % BUFFER_SIZE;
        count--;

        pthread_cond_signal(&full_cond); // 发送缓冲区非满信号
        pthread_mutex_unlock(&mutex); // 解锁

        sleep(2); // 模拟消费过程
    }
    pthread_exit(NULL);
}

int main()
{
    pthread_t producer_thread, consumer_thread;

    // 初始化互斥锁和条件变量
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&full_cond, NULL);
    pthread_cond_init(&empty_cond, NULL);

    // 创建生产者和消费者线程
    pthread_create(&producer_thread, NULL, producer, NULL);
    pthread_create(&consumer_thread, NULL, consumer, NULL);

    // 等待线程结束
    pthread_join(producer_thread, NULL);
    pthread_join(consumer_thread, NULL);

    // 销毁互斥锁和条件变量
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&full_cond);
    pthread_cond_destroy(&empty_cond);

    return 0;
}
(5)自旋锁

        自旋锁与互斥锁类似,但其机制不同。自旋锁是一种基于忙等待的锁机制,它不会使线程进入睡眠状态,而是会循环检查锁的状态,直到获取到锁为止。

        这种特性使得自旋锁适用于一些轻量级的锁定场景,如临界区很小或者锁被占用的时间很短的情况下。

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

pthread_spinlock_t spinlock; // 定义自旋锁变量
int shared_resource = 0;     // 全局共享资源

// 线程函数1,增加 shared_resource 的值
void *thread_func1(void *arg)
{
    int i;
    for (i = 0; i < 1000000; ++i)
    {
        pthread_spin_lock(&spinlock); // 加锁,等待直到自旋锁可用

        // 临界区:访问共享资源 shared_resource
        shared_resource++; // 模拟对共享资源的增加操作

        pthread_spin_unlock(&spinlock); // 解锁,释放自旋锁
    }
    return NULL;
}

// 线程函数2,减少 shared_resource 的值
void *thread_func2(void *arg)
{
    int i;
    for (i = 0; i < 1000000; ++i)
    {
        pthread_spin_lock(&spinlock); // 加锁,等待直到自旋锁可用

        // 临界区:访问共享资源 shared_resource
        shared_resource--; // 模拟对共享资源的减少操作

        pthread_spin_unlock(&spinlock); // 解锁,释放自旋锁
    }
    return NULL;
}

int main()
{
    pthread_t tid1, tid2; // 定义线程ID变量

    pthread_spin_init(&spinlock, 0); // 初始化自旋锁,参数0表示锁的作用范围为线程间共享

    // 创建两个线程,分别执行 thread_func1 和 thread_func2 函数
    pthread_create(&tid1, NULL, thread_func1, NULL);
    pthread_create(&tid2, NULL, thread_func2, NULL);

    // 等待线程执行完毕
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    pthread_spin_destroy(&spinlock); // 销毁自旋锁

    // 输出最终的共享资源值
    printf("Final shared_resource value: %d\n", shared_resource);

    return 0;
}

  • 14
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值