理解 Linux 条件变量

1 简介

当多个线程之间因为存在某种依赖关系,导致只有当某个条件存在时,才可以执行某个线程,此时条件变量(pthread_cond_t)可以派上用场。比如:

例1: 当系统不忙(这是一个条件)时,执行扫描文件状态的线程。

例2: 多个线程组成线程池,只有当任务队列中存在任务时,才用其中一个线程去执行这个任务。为避免惊群(thrundering herd),可以采用条件变量同步线程池中的线程。

2 用法

条件变量(pthread_cond_t)必须与锁(pthread_mutex_t)一起使用。

条件变量的API:

1) pthread_cond_init

2) pthread_cond_signal / pthread_cond_broadcast

3) pthread_cond_wait / pthread_cond_timedwait

4) pthread_cond_destroy

线程A:

include <stdio.h>  
include <sys/time.h>  
include <unistd.h>  
include <pthread.h>  
include <errno.h>
...

void A_thread_run(void *arg)
{
    ...

    pthread_mutex_lock (& lock);

    // 条件满足, 发出通知
    pthread_cond_signal (& cond);

    pthread_mutex_unlock (& lock);

    ...
}

线程B:

void B_thread_run(void *arg)
{
    for ( ; ; ) {
        pthread_mutex_lock (&lock);

        /* pthread_cond_wait 原子调用: 等待条件变量, 解除锁, 然后阻塞
         * 当 pthread_cond_wait 返回,则条件变量有信号,同时上锁
         *
         * 等待条件有两种方式:条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),
         * 其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEOUT
         * 无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()
         * (或pthread_cond_timedwait(),下同)的竞争条件(Race Condition)。
         * mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),
         * 且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,
         * mutex保持锁定状态,并在线程挂起进入等待前解锁。
         * 在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。
         * 激发条件有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;
         * 而pthread_cond_broadcast()则激活所有等待线程(惊群)。
         */

        pthread_cond_wait (&cond, &lock);

        if (shutdown) {
            break;
        }

        /* Unlock */
        pthread_mutex_unlock (&lock);

        /* do your task here */
    }

    pthread_mutex_unlock (&lock);
    pthread_exit (0);
}

线程B调用pthread_cond_wait,从而阻塞在此句,等待有信号通知。pthread_cond_wait内部存在原子调用:解除锁和等待条件变量有信号。当pthread_cond_wait函数返回,表明得到了信号通知,同时上锁。

线程A用pthread_cond_signal通知调用了pthread_cond_wait的线程B。

3 避免惊群

这是个狼多肉少,僧多粥少,色鬼多美女少的时代。每当一块肉丢到狼群,就引发一群狼去争抢,但最后只有一只狼得到了肉。这就是惊群(thrundering herd)。现实世界的惊群,比如老师在课堂上每次提出一个问题,最后只找一个学生回答,时间久了,学生对这个老师的问题就倦怠了。计算机的惊群会造成服务器资源空耗。

pthread_cond_signal函数的作用是发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行.如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。

但使用pthread_cond_signal不会有“惊群现象”产生,它最多只给一个线程发信号。假如有多个线程正在阻塞等待着这个条件变量的话,那么是根据各等待线程优先级的高低确定哪个线程接收到信号开始继续执行。如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。但无论如何一个pthread_cond_signal调用最多发信一次。

4 线程池threadpool

经典的例子就是一个线程池是一个固定数目的线程的组合,其中每个线程(worker)完全可以做相同的工作。线程池包含这样一个任务(task)队列,用户向任务队列中添加任务,线程池自动派发线程去执行任务。

每个线程有特定于线程的参数(thread argument),每个任务也有特定于任务的数据(task argument)。线程函数执行任务函数,同时传递给任务函数线程参数和任务参数。

典型的例子就是每个线程包含了到数据库或其他资源的连接,任务函数可以安全地使用这些连接,因为任务函数是在线程函数中同步执行的。

下面是完整的线程池代码,原来的代码中没有特定于线程的参数,我添加了这部分代码。

threadpool.h

/*
 * 2014-06-18: last modified by cheungmine
 *
 * Copyright (c) 2011, Mathias Brossard <mathias@brossard.org>.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef _THREADPOOL_H_
#define _THREADPOOL_H_

#ifndef POOL_MAX_THREADS
#   define POOL_MAX_THREADS  256
#endif

#ifndef POOL_MAX_QUEUES
#   define POOL_MAX_QUEUES  1024
#endif

#ifndef POOL_DEFAULT_THREADS
#   define POOL_DEFAULT_THREADS  32
#endif

#ifndef POOL_DEFAULT_QUEUES
#   define POOL_DEFAULT_QUEUES  256
#endif

/**
 * @file threadpool.h
 * @brief Threadpool Header file
 */

typedef struct threadpool_t threadpool_t;

/**
 * @file threadpool.h
 * @brief thread_context_t
 *   thread can take itself argument
 *   added by cheungmine.
 *   2014-06-17
 */
typedef struct thread_context_t
{
    void *pool;
    pthread_t thread;
    void *thread_arg;
    struct threadpool_task_t *task;
} thread_context_t;

/**
 *  @struct threadpool_task
 *  @brief the work struct
 *
 *  @var function Pointer to the function that will perform the task.
 *  @var argument Argument to be passed to the function.
 */
typedef struct threadpool_task_t
{
    void (*function)(thread_context_t *);
    int    flags;     /* user defined */
    void * argument;
} threadpool_task_t;

typedef enum
{
    threadpool_invalid        = -1,
    threadpool_lock_failure   = -2,
    threadpool_queue_full     = -3,
    threadpool_shutdown       = -4,
    threadpool_run_failure    = -5,
    threadpool_out_memory     = -6
} threadpool_error_t;

static const char* threadpool_error_messages[] = {
    "threadpool_success",
    "threadpool_invalid",
    "threadpool_lock_failure",
    "threadpool_queue_full",
    "threadpool_shutdown",
    "threadpool_run_failure",
    "threadpool_out_memory"
};

/**
 * @function threadpool_create
 * @brief Creates a threadpool_t object.
 * @param thread_count Number of worker threads.
 * @param queue_size   Size of the queue.
 * @param thread_args  array of arguments with count of thread_count, NULL if ignored.
 * @param flags        Unused parameter.
 * @return a newly created thread pool or NULL
 */
threadpool_t *threadpool_create (int thread_count, int queue_size, void **thread_args, int flags);

/**
 * @function threadpool_add
 * @brief add a new task in the queue of a thread pool
 * @param pool     Thread pool to which add the task.
 * @param function Pointer to the function that will perform the task.
 * @param argument Argument to be passed to the function.
 * @param flags    Unused parameter.
 * @return 0 if all goes well, negative values in case of error (@see
 * threadpool_error_t for codes).
 */
int threadpool_add (threadpool_t *pool, void (*routine)(thread_context_t *), void *task_arg, int flags);

/**
 * @function threadpool_destroy
 * @brief Stops and destroys a thread pool.
 * @param pool  Thread pool to destroy.
 * @param flags Unused parameter.
 */
int threadpool_destroy (threadpool_t *pool, int flags);

#endif /* _THREADPOOL_H_ */

threadpool.c

/*
 * 2014-06-18: last modified by cheungmine
 *
 * Copyright (c) 2011, Mathias Brossard <mathias@brossard.org>.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @file threadpool.c
 * @brief Threadpool implementation file
 */

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

#include "threadpool.h"

/**
 *  @struct threadpool
 *  @brief The threadpool struct
 *
 *  @var notify       Condition variable to notify worker threads.
 *  @var threads      Array containing worker threads ID.
 *  @var thread_count Number of threads
 *  @var queue        Array containing the task queue.
 *  @var queue_size   Size of the task queue.
 *  @var head         Index of the first element.
 *  @var tail         Index of the next element.
 *  @var shutdown     Flag indicating if the pool is shutting down
 */
struct threadpool_t {
    pthread_mutex_t lock;
    pthread_cond_t notify;

    int head;
    int tail;
    int count;
    int shutdown;
    int started;

    int thread_count;
    int queue_size;

    threadpool_task_t *queues;
    thread_context_t thread_ctxs[0];
};

/**
 * @function void *threadpool_run(void *threadpool)
 * @brief the worker thread
 * @param threadpool the pool which own the thread
 */
static void *threadpool_run (void *threadpool);

int threadpool_free(threadpool_t *pool);

threadpool_t *threadpool_create(int thread_count, int queue_size, void **thread_args, int flags)
{
    int i;
    threadpool_t *pool = NULL;

    /* Check thread_count for negative or otherwise very big input parameters */
    if (thread_count < 0 || thread_count > POOL_MAX_THREADS) {
        goto err;
    }
    if (thread_count == 0) {
        thread_count = POOL_DEFAULT_THREADS;
    }

    /* Check queue_size for negative or otherwise very big input parameters */
    if (queue_size < 0 || queue_size > POOL_MAX_QUEUES) {
        goto err;
    }
    if (queue_size == 0) {
        queue_size = POOL_DEFAULT_QUEUES;
    }

    /* create threadpool */
    if ( (pool = (threadpool_t *) malloc (sizeof(threadpool_t) +
        sizeof(thread_context_t) * thread_count +
        sizeof(threadpool_task_t) * queue_size)) == NULL ) {
        goto err;
    }

    /* Initialize */
    pool->thread_count = thread_count;
    pool->queue_size = queue_size;
    pool->head = pool->tail = pool->count = 0;
    pool->shutdown = pool->started = 0;
    pool->queues = (threadpool_task_t *) (& pool->thread_ctxs[thread_count]);

    /* Initialize mutex and conditional variable first */
    if ((pthread_mutex_init (&(pool->lock), NULL) != 0) ||
       (pthread_cond_init (&(pool->notify), NULL) != 0)) {
        goto err;
    }

    /* Start worker threads */
    for (i = 0; i < thread_count; i++) {
        thread_context_t * pctx = & pool->thread_ctxs[i];

        /* set pool to each thread context */
        pctx->pool = (void*) pool;

        /* assign thread argument if valid */
        if (thread_args) {
            pctx->thread_arg = thread_args[i];
        } else {
            pctx->thread_arg = 0;
        }

        if ( pthread_create (& pctx->thread, NULL, threadpool_run, (void*) pctx) != 0) {
            threadpool_destroy (pool, 0);
            return NULL;
        } else {
            pool->started++;
        }
    }

    return pool;

 err:
    if(pool) {
        threadpool_free(pool);
    }
    return NULL;
}

int threadpool_add (threadpool_t *pool, void (*function)(thread_context_t *), void *task_arg, int flags)
{
    int err = 0;
    int next;

    if ( pool == NULL || function == NULL ) {
        return threadpool_invalid;
    }

    if (pthread_mutex_lock (&(pool->lock)) != 0) {
        return threadpool_lock_failure;
    }

    next = pool->tail + 1;
    next = (next == pool->queue_size) ? 0 : next;

    do {
        /* Are we full ? */
        if (pool->count == pool->queue_size) {
            err = threadpool_queue_full;
            break;
        }

        /* Are we shutting down ? */
        if (pool->shutdown) {
            err = threadpool_shutdown;
            break;
        }

        /* Add task to queue */
        pool->queues[pool->tail].function = function;
        pool->queues[pool->tail].argument = task_arg;
        pool->queues[pool->tail].flags = flags;

        pool->tail = next;
        pool->count += 1;

        /* pthread_cond_broadcast */
        if (pthread_cond_signal (&(pool->notify)) != 0) {
            err = threadpool_lock_failure;
            break;
        }
    } while(0);

    if (pthread_mutex_unlock (&pool->lock) != 0) {
        err = threadpool_lock_failure;
    }

    return err;
}

int threadpool_destroy (threadpool_t *pool, int flags)
{
    int i, err = 0;

    if (pool == NULL) {
        return threadpool_invalid;
    }

    if (pthread_mutex_lock (&(pool->lock)) != 0) {
        return threadpool_lock_failure;
    }

    do {
        /* Already shutting down */
        if (pool->shutdown) {
            err = threadpool_shutdown;
            break;
        }

        pool->shutdown = 1;

        /* Wake up all worker threads */
        if ((pthread_cond_broadcast(&(pool->notify)) != 0) ||
           (pthread_mutex_unlock(&(pool->lock)) != 0)) {
            err = threadpool_lock_failure;
            break;
        }

        /* Join all worker thread */
        for (i = 0; i < pool->thread_count; i++) {
            if (pthread_join (pool->thread_ctxs[i].thread, NULL) != 0) {
                err = threadpool_run_failure;
            }
        }
    } while(0);

    if (pthread_mutex_unlock (&pool->lock) != 0) {
        err = threadpool_lock_failure;
    }

    /* Only if everything went well do we deallocate the pool */
    if (!err) {
        threadpool_free (pool);
    }
    return err;
}

int threadpool_free (threadpool_t *pool)
{
    if (pool == NULL || pool->started > 0) {
        return -1;
    }

    pthread_mutex_lock (&(pool->lock));
    pthread_mutex_destroy (&(pool->lock));
    pthread_cond_destroy (&(pool->notify));

    free(pool);
    return 0;
}

/**
 * each thread run function
 */
static void *threadpool_run (void * param)
{
    threadpool_task_t task;

    thread_context_t * thread_ctx = (thread_context_t *) param;
    threadpool_t * pool = thread_ctx->pool;

    for ( ; ; ) {
        /* Lock must be taken to wait on conditional variable */
        pthread_mutex_lock (&(pool->lock));

        /* Wait on condition variable, check for spurious wakeups.
           When returning from pthread_cond_wait(), we own the lock. */
        while ((pool->count == 0) && (!pool->shutdown)) {
            pthread_cond_wait (&(pool->notify), &(pool->lock));
        }

        if (pool->shutdown) {
            break;
        }

        /* Grab our task */
        task.function = pool->queues[pool->head].function;
        task.argument = pool->queues[pool->head].argument;
        task.flags    = pool->queues[pool->head].flags;

        thread_ctx->task = &task;

        pool->head += 1;
        pool->head = (pool->head == pool->queue_size) ? 0 : pool->head;
        pool->count -= 1;

        /* Unlock */
        pthread_mutex_unlock (&(pool->lock));

        /* Get to work */
        (*(task.function)) (thread_ctx);
    }

    pool->started--;

    pthread_mutex_unlock (&(pool->lock));
    pthread_exit (NULL);
    return (NULL);
}

理解 Linux 条件变量的更多相关文章

  1. linux 条件变量与线程池

    条件变量Condition Variables 概述 1. 条件变量提供了另外一种线程同步的方式.如果没有条件变量,程序需要使用线程连续轮询(可能在临界区critical section内)方式检查条 ...

  2. linux 条件变量

    互斥量就是一把锁,在访问数据时能保证同一时间内只有一个线程访问数据,在访问完以后再释放互斥量上的锁. 条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条 ...

  3. linux条件变量

    条件变量用于线程之间的通信,和互斥锁一起使用.条件变量用于及时通知等待的线程条件的变化,使线程不至于错过变化. 考虑下面的情况,有AB两个线程对index这个全局变量进行++,一个线程C用于判断,in ...

  4. linux条件变量使用和与信号量的区别

    近来在项目中用到条件变量和信号量做同步时,这一块一直都有了解,但也一直没有总结,这次总结一下,给大家提供点参考,也给自己留点纪念. 首先,关于信号量和条件变量的概念可以自行查看APUE,我这直接把AP ...

  5. Linux 条件变量函数signal和wait补充

    pthread_cond_wait必须放在pthread_mutex_lock和pthread_mutex_unlock之间,因为他要根据共享变量的状态来觉得是否要等待,而为了不永远等待下去所以必须要 ...

  6. linux多线程同步pthread_cond_XXX条件变量的理解

    在linux多线程编程中,线程的执行顺序是不可预知的,但是有时候由于某些需求,需要多个线程在启动时按照一定的顺序执行,虽然可以使用一些比较简陋的做法,例如:如果有3个线程 ABC,要求执行顺序是A-- ...

  7. Linux同步机制(二) - 条件变量,信号量,文件锁,栅栏

    1 条件变量 条件变量是一种同步机制,允许线程挂起,直到共享数据上的某些条件得到满足. 1.1 相关函数 #include <pthread.h>  pthread_cond_t cond ...

  8. Linux再谈互斥锁与条件变量

    原文地址:http://blog.chinaunix.net/uid-27164517-id-3282242.html pthread_cond_wait总和一个互斥锁结合使用.在调用pthread_ ...

  9. Linux Qt使用POSIX多线程条件变量、互斥锁(量)

    今天团建,但是文章也要写.酒要喝好,文要写美,方为我辈程序员的全才之路.嘎嘎 之前一直在看POSIX的多线程编程,上个周末结合自己的理解,写了一个基于Qt的用条件变量同步线程的例子.故此来和大家一起分 ...

随机推荐

  1. 探究java接口中的变量与方法

    关于变量 java接口里的变量都是默认 pubic static final的 为啥? public 接口得能被所有对象调用 static 这个变量是属于接口本身,而不是实现了接口的对象的 具体来说  ...

  2. ubuntu安装qq

    安装的版本是国际版 1.安装依赖库 sudo apt-get install libgtk2.0-0:i386 sudo apt-get install lib32ncurses5 2.下载 下载链接 ...

  3. 剑指Offer——求职必备神器

    剑指Offer--求职必备神器 前言   不管是公司网申.银行招聘.面试等等,"谈谈你的职业规划"."以往工作中遇到了哪些棘手问题?你是如何解决的?".&quo ...

  4. JAVA面向对象-----接口与类、接口之间的关系

    接口与类.接口之间的关系 大家之前都知道类与类之间的关系继承,那么接口与类之间又是怎样子的关系呢? 接口与类之间是实现关系.非抽象类实现接口时,必须把接口里面的所有方法实现.类实现接口用关键字impl ...

  5. JSP自定义方法库

    如果JSTL的方法库没有满足需要,可以使用自定义方法进行扩展 public class Function{ public static int length(Object obj){ //返回对象的长 ...

  6. Volley的基本使用

    Volley的基本使用 导入jar包 导入成功 源码 GET请求下载地址:http://download.csdn.net/detail/q4878802/9053969 POST请求下载地址:htt ...

  7. MPI二维笛卡尔坐标划分【1】

    本文简单演示,如何对现有进程进行二维划分,如何获得进程的X和Y坐标. 只有一段程序: #include <mpi.h> #include <stdio.h> #include ...

  8. 剑指Offer——CVTE校招笔试题+知识点总结(Java岗)

    剑指Offer(Java岗)--CVTE校招笔试题+知识点总结 2016.9.3 19:00参加CVTE笔试,笔试内容如下: 需要掌握的知识:Linux基本命令.网络协议.数据库.数据结构. 选择题 ...

  9. Java中读取Excel功能实现_POI

    这里使用apache的poi进行读取excel 1,新建javaproject 项目:TestExcel 2,导入包 包下载地址:http://poi.apache.org/download.html ...

  10. Django 表单校验 表单字段设置 自定义表单校验规则

    今天看到了一篇非常好的博文,拿来和大家分享一下. 内容包括了: 用户注册时输入数据的校验 使用widget进行字段设置 实现自定义的校验规则 参考自下面的这篇文章