条件变量--使两个线程实现交替打印

2023-12-14 17:29:13

一、介绍

什么是条件变量?

条件变量(Condition Variable)是多线程编程中用于线程间通信和同步的一种机制。它通常与互斥锁(Mutex)一起使用,用于解决线程竞争和避免忙等待的问题。(条件变量不能单独使用)

条件变量解决的主要问题是当一个线程需要等待某个条件变成真时,它可以释放互斥锁,让其他线程有机会执行。当条件变成真时,线程可以重新获得互斥锁并继续执行。能提高线程的效率,避免了一些不必要的忙等待。

条件变量通常与以下三个函数一起使用:

pthread_cond_init: 初始化条件变量。

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

pthread_cond_wait: 在等待条件变为真的同时释放互斥锁,将线程挂起。

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

pthread_cond_signal / pthread_cond_broadcast: 用于通知等待条件变为真的线程。

  • pthread_cond_signal:通知等待队列中的一个线程。
  • pthread_cond_broadcast:通知等待队列中的所有线程。
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);

使用条件变量时,几种典型的模式:

  1. 线程在互斥锁的保护下检查条件是否满足。
  2. 如果条件不满足,线程调用 pthread_cond_wait 来等待条件的变化,同时释放互斥锁。
  3. 当其他线程满足条件时,它们会调用 pthread_cond_signalpthread_cond_broadcast 来通知等待的线程。
  4. 被通知的线程醒来,重新获得互斥锁,再次检查条件是否满足。如果条件满足,它继续执行,否则它可能再次等待。

二、使用举例

题目描述:

使用两个线程实现交替打印“hello thread A”,“hello thread B”,要求不能出现连续的同一个线程进行打印。

解决方法:

定义一个互斥锁(g_lock)用于保护共享资源,确保一次只有一个线程可以访问临界区,避免数据竞争;

定义两个条件变量(thread_a_cond、thread_b_cond)用于控制线程间的通信。

定义一个变量(g_is_my_turn)控制两个线程的执行顺序。

定义两个函数thread_a_start、thread_b_start,分别让两个线程来调用。

void *thread_a_start(void *arg)
{
    (void)arg;
    while (1)
    {
        pthread_mutex_lock(&g_lock);

        while (g_is_my_turn == 1)
        {
            pthread_cond_wait(&thread_a_cond, &g_lock);
        }

        printf("hello thread A\n");
        sleep(1);
        g_is_my_turn++;
        pthread_mutex_unlock(&g_lock);
        pthread_cond_signal(&thread_b_cond);
    }
}
  • arg 参数未被使用,因此在函数体内使用 (void)arg; 这样的语句是为了消除编译器可能产生的未使用参数的警告。
  • 线程A通过互斥锁保护临界区。
  • g_is_my_turn 为1时,线程A等待条件变量 thread_a_cond,直到线程B通知。
  • 打印信息,休眠1秒,然后通过递增 g_is_my_turn 来通知线程B可以执行。
  • 解锁互斥锁,并通过条件变量 thread_b_cond 通知线程B。
void *thread_b_start(void *arg)
{
    (void)arg;
    while (1)
    {
        sleep(1);
        pthread_mutex_lock(&g_lock);
        while (g_is_my_turn == 0)
        {
            pthread_cond_wait(&thread_b_cond, &g_lock);
        }
        printf("hello thread B\n");
        sleep(1);
        g_is_my_turn--;
        pthread_mutex_unlock(&g_lock);
        pthread_cond_signal(&thread_a_cond);
    }
}
  • 线程B休眠1秒,然后通过互斥锁保护临界区。
  • g_is_my_turn 为0时,线程B等待条件变量 thread_b_cond,直到线程A通知。
  • 打印信息,休眠1秒,然后通过递减 g_is_my_turn 来通知线程A可以执行。
  • 解锁互斥锁,并通过条件变量 thread_a_cond 通知线程A。

主函数调用

  • 初始化互斥锁和条件变量。
  • 创建两个线程,分别执行线程A和线程B的函数。
  • 等待两个线程的结束。
  • 销毁互斥锁和条件变量。
int main()
{
    // 初始化
    pthread_mutex_init(&g_lock, NULL);
    pthread_cond_init(&thread_a_cond, NULL);
    pthread_cond_init(&thread_b_cond, NULL);
    // 定义两个线程
    pthread_t thread_a, thread_b;
    
    int ret = pthread_create(&thread_a, NULL, thread_a_start, NULL);
    if (ret < 0)
    {
        perror("pthread_create error");
        return 0;
    }
    ret = pthread_create(&thread_b, NULL, thread_b_start, NULL);
    if (ret < 0)
    {
        perror("pthread_create error");
        return 0;
    }

    pthread_join(thread_a, NULL);
    pthread_join(thread_b, NULL);
    pthread_mutex_destroy(&g_lock);
    pthread_cond_destroy(&thread_a_cond);
    pthread_cond_destroy(&thread_b_cond);
    return 0;
}

函数解析:

pthread_create 函数中,各个参数的含义如下:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
  1. thread 一个指向 pthread_t 类型的变量的指针,用于存储新创建线程的标识符。
  2. attr 一个指向 pthread_attr_t 类型的变量的指针,用于指定线程的属性。一般使用NULL。
  3. start_routine 是一个函数指针,指向线程的起始函数。这个函数必须返回 void * 类型,接受一个 void * 类型的参数。线程将从这个函数开始执行。该函数的参数只能有且一个void*类型。
  4. arg 传递给 start_routine 函数的参数,这里是一个 void * 类型的指针。通常,可以使用这个参数来向线程传递一些数据。

pthread_join(thread_a, NULL);

  • pthread_join 用于等待指定的线程终止。
  • hread_a 是被等待的线程。
  • 第二个参数是一个指向线程返回值的指针,这里使用 NULL 表示不关心线程的返回值。
  • 当调用 pthread_join 后,主线程会一直阻塞,直到指定的线程(thread_a)终止。

pthread_join(thread_b, NULL);同上。

pthread_mutex_destroy(&g_lock);

  • pthread_mutex_destroy 用于销毁互斥锁。
  • 在线程使用完互斥锁后,调用该函数释放相关资源。

pthread_cond_destroy(&thread_a_cond); pthread_cond_destroy(&thread_b_cond);

  • pthread_cond_destroy 用于销毁条件变量。
  • 在线程使用完条件变量后,调用该函数释放相关资源。

线程同步逻辑

线程A逻辑:

  • g_is_my_turn == 0,表示现在是线程A的执行时机。
  • 执行任务,通过递增 g_is_my_turn 使线程B可以执行。
  • 通过条件变量 thread_b_cond 来通知线程B。

线程B逻辑:

  • g_is_my_turn == 1,表示现在是线程B的执行时机。
  • 执行任务,通过递减 g_is_my_turn 使线程A可以执行。
  • 通过条件变量 thread_a_cond 来通知线程A。

运行截图:

文章来源:https://blog.csdn.net/qzt__l0ve/article/details/134995082
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。