理解 Linux 条件变量
理解 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 条件变量的更多相关文章
- linux 条件变量与线程池
条件变量Condition Variables 概述 1. 条件变量提供了另外一种线程同步的方式.如果没有条件变量,程序需要使用线程连续轮询(可能在临界区critical section内)方式检查条 ...
- linux 条件变量
互斥量就是一把锁,在访问数据时能保证同一时间内只有一个线程访问数据,在访问完以后再释放互斥量上的锁. 条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条 ...
- linux条件变量
条件变量用于线程之间的通信,和互斥锁一起使用.条件变量用于及时通知等待的线程条件的变化,使线程不至于错过变化. 考虑下面的情况,有AB两个线程对index这个全局变量进行++,一个线程C用于判断,in ...
- linux条件变量使用和与信号量的区别
近来在项目中用到条件变量和信号量做同步时,这一块一直都有了解,但也一直没有总结,这次总结一下,给大家提供点参考,也给自己留点纪念. 首先,关于信号量和条件变量的概念可以自行查看APUE,我这直接把AP ...
- Linux 条件变量函数signal和wait补充
pthread_cond_wait必须放在pthread_mutex_lock和pthread_mutex_unlock之间,因为他要根据共享变量的状态来觉得是否要等待,而为了不永远等待下去所以必须要 ...
- linux多线程同步pthread_cond_XXX条件变量的理解
在linux多线程编程中,线程的执行顺序是不可预知的,但是有时候由于某些需求,需要多个线程在启动时按照一定的顺序执行,虽然可以使用一些比较简陋的做法,例如:如果有3个线程 ABC,要求执行顺序是A-- ...
- Linux同步机制(二) - 条件变量,信号量,文件锁,栅栏
1 条件变量 条件变量是一种同步机制,允许线程挂起,直到共享数据上的某些条件得到满足. 1.1 相关函数 #include <pthread.h> pthread_cond_t cond ...
- Linux再谈互斥锁与条件变量
原文地址:http://blog.chinaunix.net/uid-27164517-id-3282242.html pthread_cond_wait总和一个互斥锁结合使用.在调用pthread_ ...
- Linux Qt使用POSIX多线程条件变量、互斥锁(量)
今天团建,但是文章也要写.酒要喝好,文要写美,方为我辈程序员的全才之路.嘎嘎 之前一直在看POSIX的多线程编程,上个周末结合自己的理解,写了一个基于Qt的用条件变量同步线程的例子.故此来和大家一起分 ...
随机推荐
- Android中ViewFlipper的使用详解
说到android的左右滑动效果我们可以说是在每个应用上面都可以看到这样的效果,不管是微博,还是QQ等. 实现左右滑动的方式很多,有ViewPager(不过这个和需要android-support-v ...
- T-SQL动态查询(2)——关键字查询
接上文:T-SQL动态查询(1)--简介 前言: 在开发功能的过程中,我们常常会遇到类似以下情景:应用程序有一个查询功能,允许用户在很多查询条件中选择所需条件.这个也是本系列的关注点. 但是有时候你也 ...
- Android ListView中Item点击事件失效解决方案
欢迎关注公众号,每天推送Android技术文章,二维码如下:(可扫描) 在平常的开发过程中,我们的ListView可能不只是简单的显示下文本或者按钮,更多的是显示复杂的布局,这样的话,我们就得自己写布 ...
- UE4联机烘焙
联机烘焙就是为了利用多台电脑解决烘焙效率的问题 1.UE4的烘焙工具在安装目录下的\Engine\Binaries\DotNET,比如我这里是E:\UnrealEngine-release\Engin ...
- javascript之正则表达式
创建正则表达式的两种方法 显式: new RegExp("pattern"[,"flags"]); 例 var regex = new ("abc&q ...
- Description Resource Path Location Type AndroidManifest.xml file missing!
这个问题又找了好久.国内回答的确不敢恭维. 本回答来自谷歌: This is build issue. Go to Menu in eclipse, Project>clean then P ...
- antlr v4 使用指南连载1——简介
antlr v4简介 antlr是一个强大语言解析工具,可以用于处理结构化文本.二进制文件.说白了,其实可以这么认为,antlr是一个更强大的正则表达式工具.它可以完成更多正则表达式无法 ...
- 【一天一道LeetCode】#345. Reverse Vowels of a String
一天一道LeetCode 本系列文章已全部上传至我的github,地址:ZeeCoder's Github 欢迎大家关注我的新浪微博,我的新浪微博 欢迎转载,转载请注明出处 (一)题目 Write a ...
- ubuntu权限管理常用命令
1.chmod 第一种方式 chomd [{ugoa}{+-=}{rwx}] [文件或者目录] u 代表该文件所属用户 g 代表该文件所属用户组 o 代表访客 a 代表所有用户 +-=分别表示增加权限 ...
- UNIX网络编程——I/O复用:select和poll函数
我们看到TCP客户同时处理两个输入:标准输入和TCP套接字.我们遇到的问题是就在客户阻塞于(标准输入上)fgets调用,服务器进程会被杀死.服务器TCP虽然正确的给客户TCP发送了一个FIN,但是既然 ...