Linux多线程编程条件变量的概述和使用方法

目录

概述

1 引入条件变量概念

2 条件变量的应用

2.1 创建与销毁

2.1.1 创建条件变量

2.1.2 销毁条件变量

2.2 等待与通知

2.2.1 等待

2.2.2 通知

3 使用条件变量的范例

3.1 编写代码

3.2 测试

4 参考文献


概述

本文介绍了linux多线程编程中条件变量的相关知识,包括概念,函数体等内容,还编写一个实用的案例来介绍条件变量在实际编程中的使用方法。并在板卡中验证它的功能。

1 引入条件变量概念

在多线程编程中仅使用互斥锁来完成互斥是不够用的, 如以下情形:假设有两个线程 thread-1 和 thread-2, 需要这个两个线程循环对一个共享变量 sum 进行自增操作,那么 thread-1 和thread-2 只需要使用互斥量即可保证操作正确完成,线程执行代码如所示:

pthread_mutex_t sumlock= PTHREAD_MUTEX_INITIALIZER;
​
void * thread1(void) 
{
    pthread_mutex_lock(&sumlock);
    sum++;
    pthread_mutex_unlock(&sumlock);
}
​
void * thread2(void) 
{
    pthread_mutex_lock(&sumlock);
    sum++;
    pthread_mutex_unlock(&sumlock);
}

如果这时需要增加另一个线程 thread-3,需要 thread-3 在 count 大于 100 时将 count 值重新置 0 值, 那么可以 thread-3 可以实现如下:

void * thread3 (void) 
{
    pthread_mutex_lock(&sumlock);
    if (sum >= 100) {
        sum = 0;
        pthread_mutex_unlock(&sumlock);
    } 
    else {
        pthread_mutex_unlock(&sumlock);
        usleep(100);
    }
}

以上代码存在以下问题:

1) sum 在大多数情况下不会到达 100, 那么对 thread-3 的代码来说,大多数情况下执行 else分支, 只是 lock 和 unlock,然后进入 sleep(), 这浪费了 CPU 处理时间。

2) 为了节省 CPU 处理时间, thread-3 会在探测到 sum 没到达 100 的时候 usleep()一段时间。这样却又带来另外一个问题: 即 thread-3 响应速度下降。 可能在 sum 到达 200 的时候, thread-3 才会醒过来。

怎么解决这个问题呢?

这样时间与效率出现了矛盾,而条件变量就是解决这个问题的好方法。

2 条件变量的应用

2.1 创建与销毁

2.1.1 创建条件变量

pthreads 用 pthread_cond_t 类型的变量来表示条件变量。程序必须在使用 pthread_cond_t 变量之前对其进行初始化。

(1) 静态初始化

对于静态分配的变量可以简单地将 PTHREAD_COND_INITIALIZER 赋值给变量来初始化默认行为的条件变量。

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

(2)动态初始化

对动态分配或者不使用默认属性的条件变量来说可以使用 pthread _cond_init()来初始化。函数原型如下:

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

参数介绍:

参数 描述
cond 参数 cond 是一个指向需要初始化 pthread_cond_t 变量的指针
attr 参数 attr 传递 NULL 值时,pthread_cond_init()将 cond 初始化为默认属性的条件变量。函数成功将返回 0;否则返回一个非 0 的错误码。

静态初始化程序通常比调用 pthread_cond_init()更有效,而且在任何线程开始执行之前,确保变量被执行一次。

一个条件变量的初始化的案例 :

pthread_cond_t cond;
int error;
​
if (error = pthread_cond_init(&cond, NULL)){
   fprintf(stderr, "Failed to initialize cond : %s\n", strerror(error));
 }

2.1.2 销毁条件变量

函数 pthread_cond_destroy()用来销毁它参数所指出的条件变量,函数原型如下:

int pthread_cond_destroy(pthread_cond_t *cond);

函数成功调用返回 0,否则返回一个非 0 的错误码。以下代码演示了如何销毁一个条件变量。

pthread_cond_t cond;
int error;
​
if (error = pthread_cond_destroy(&cond)){
     fprintf(stderr, "Failed to destroy cond : %s\n", strerror(error));
 }

2.2 等待与通知

2.2.1 等待

条件变量是与条件测试一起使用的,通常线程会对一个条件进行测试,如果条件不满足就会调用条件等待函数来等待条件满足。

条件等待函数如下:

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

功能介绍

1)pthread_cond_wait()函数: 在条件不满足时将一直等待

2 pthread_cond_timedwait() 函数: 等待一段时间。

pthread_cond_timedwait() 函数参数介绍:

参数 描述
cond 指向条件变量的指针
mutex 指向互斥量的指针,线程在调用前应该拥有这个互斥量,当线程要加入条件变量的等待队列时,等待操作会使线程释放这个互斥量
abstime 返回时间的指针,如果条件变量通知信号没有在此等待时间之前出现,等待将超时退出, abstime 是个绝对时间,而不是时间间隔

函数返回值:

成功调用返回 0,否则返回非 0 的错误码,其中 pthread_cond_timedwait() 函数如果 abstime 指定的时间到期,错误码为 ETIMEOUT。

一个实例:

void function()
{
    pthread_mutex_lock(&mutex)
    
    while(a < b){
        pthread_cond_wait(&cond, &mutex)
    }
    
    pthread_mutex_unlock(&mutex)
}

2.2.2 通知

当另一个线程修改了某参数可能使得条件变量所关联的条件变成真时,它应该通知一个或者多个等待在条件变量等待队列中的线程。

函数名 功能介绍
pthread_cond_signal() 可以唤醒一个在条件变量等待队列等待的线程
pthread_cond_broadcast() 可以所有在条件变量等待队列等待的线程

函数原型如下:

int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

参数 cond 是一个指向条件变量的指针。函数成功返回 0,否则返回一个非 0 的错误码。

3 使用条件变量的范例

3.1 编写代码

创建 test_thread.c文件,编写如下代码:

/***************************************************************
Copyright  2024-2029. All rights reserved.
文件名     :  test_thread.c
作者       : tangmingfei2013@126.com
版本       : V1.0
描述       : pthread API test
其他       : 无
日志       : 初版V1.0 2024/03/04
​
***************************************************************/
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <linux/fs.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
​
static pthread_t tid[3];
static int sum = 0;
static pthread_mutex_t sumlock = PTHREAD_MUTEX_INITIALIZER;        /* 静态初始化互斥量 */
static pthread_cond_t cond_sum_ready = PTHREAD_COND_INITIALIZER;   /* 静态初始化条件变量 */
​
void * thread_12(void *arg) 
{
    int i;
    long id = (long)arg;
​
    for (i = 0; i < 60; i++) {
        pthread_mutex_lock(&sumlock);             /* 使用互斥量保护临界变量 */
        sum++;
        printf("thread %ld: read sum value = %d\n", id + 1 , sum);
        pthread_mutex_unlock(&sumlock);
        
        if (sum >= 100){
            pthread_cond_signal(&cond_sum_ready); /* 发送条件通知,唤醒等待线程 */
        }
    }
    
    return NULL;
}
​
void * thread_3(void *arg) 
{
    pthread_mutex_lock(&sumlock);
    
    while(sum < 100){                                 /* 不满足条件将一直等待 */
        pthread_cond_wait(&cond_sum_ready, &sumlock); /* 等待条件满足 */
    }
    sum = 0;
    printf("t3: clear sum value\n");
    
    pthread_mutex_unlock(&sumlock);
    
    return NULL;
}
​
int main(void) 
{
    int err;
    long i;
    
    for (i = 0; i < 2; i++) 
    {
         /* 创建线程 1 线程 2 */
        err = pthread_create(&(tid[i]), NULL, &thread_12, (void *)i); 
        if (err != 0) {
            printf("Can't create thread :[%s]", strerror(err));
        } 
    }
    
    err = pthread_create(&(tid[2]), NULL, &thread_3, NULL);    /* 创建线程 3 */
    if (err != 0)
        printf("Can't create thread :[%s]", strerror(err));
    
    for (i = 0; i < 3; i++){
        pthread_join(tid[i], NULL);
    }
    
    return 0;
}
​

3.2 测试

编译代码,然后在板卡上运行:

1)线程2首先得到互斥锁,count++,一直加到60

2)线程1得到互斥锁,count++,一直加到100 , 发出条件通知

2)线程3得到互斥锁,收到条件通知,然后清除计数,解锁互斥量

4 参考文献

  1. 《现代操作系统》

  2. 《linux/unix系统编程手册》

最近更新

  1. TCP协议是安全的吗?

    2024-03-12 07:16:04       19 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-12 07:16:04       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-12 07:16:04       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-12 07:16:04       20 阅读

热门阅读

  1. 华为交换机创建端口组

    2024-03-12 07:16:04       32 阅读
  2. mysql用 法like concat()

    2024-03-12 07:16:04       22 阅读
  3. ES6基础3

    2024-03-12 07:16:04       25 阅读
  4. XR虚拟拍摄引领短剧创新风潮

    2024-03-12 07:16:04       28 阅读
  5. OpenXR 超详细的spec--Chapter 1 Introduce

    2024-03-12 07:16:04       24 阅读
  6. django默认后台管理显示内容深化设置

    2024-03-12 07:16:04       24 阅读
  7. 微信小程序 doc

    2024-03-12 07:16:04       23 阅读
  8. [Django 0-1] 源码分析

    2024-03-12 07:16:04       20 阅读
  9. HTTP相关

    2024-03-12 07:16:04       21 阅读