advacing lnux program 条件变量[copy]

我们已经展示了如何在两个线程同时访问一个变量的时候利用互斥体进行保护,以及如
何使用信号量实现共享的计数器。条件变量是GNU/Linux提供的第三种同步工具;利用它
你可以在多线程环境下实现更复杂的条件控制。
假设你要写一个永久循环的线程,每次循环的时候执行一些任务。不过这个线程循环需
要被一个标志控制:只有当标志被设置的时候才运行,标志被清除的时候线程暂停。
代码列表4.13显示了你可以通过在不断自旋(重复循环)以实现这一点。每次循环的
时候,线程都检查这个标志是否被设置。因为有多个线程都要访问这个标志,我们使用一个
互斥体保护它。这种实现虽然可能是正确的,但是效率不尽人意。当标志没有被设置的时候,
线程会不断循环检测这个标志,同时会不断锁定、解锁互斥体,浪费CPU 时间。你真正需
要的是这样一种方法:当标志没有设置的时候让线程进入休眠状态;而当某种特定条件出现

时,标志位被设置,线程被唤醒。
代码列表4.13 (spin-condvar.c) 一个简单的条件变量实现
#include <pthread.h>
int thread_flag;
pthread_mutex_t thread_flag_mutex;
void initialize_flag()
{
pthread_mutex_init (&thread_flag_mutex, NULL);
thread_flag = 0;
}
/* 当标志被设置的时候反复调用do_work,否则自旋等待。*/
void* thread_function (void* thread_arg)
{
while (1) {
int flag_is_set;
/* 用一个互斥体保护标志。*/
pthread-mutex_lock (&thread_flag_mutex);
flag_is_set = thread_flag;
pthread_mutex_unlock (&thread_flag_mutex);
if (flag_is_set)
do_work ();
/* 否则什么也不做,直接进入下一次循环。*/
}
return NULL;
}
/* 将线程标志的值设置为flag_value。*/
void set_thread_flag (int flag_value)
{
/* 用一个互斥体保护线程标志。*/
pthread_mutex_lock (&thread_flag_mutex);
thread_flag = flag_value;
pthread_mutex_unlock (&thread-flag_mutex);
}
条件变量将允许你实现这样的目的:在一种情况下令线程继续运行,而相反情况下令线

程阻塞。只要每个可能涉及到改变状态的线程正确使用条件变量,Linux将保证当条件改变
的时候由于一个条件变量的状态被阻塞的线程均能够被激活。
如同信号量,线程可以对一个条件变量执行等待操作。如果线程A正在等待一个条件
变量,它会被阻塞直到另外一个线程,设为线程B,向同一个条件变量发送信号以改变其
状态。不同于信号量,条件变量没有计数值,也不占据内存空间;线程A必须在B发送信
号之前开始等待。如果B在A执行等待操作之前发送了信号,这个信号就丢失了,同时A
会一直阻塞直到其它线程再次发送信号到这个条件变量。

你可以这样使用条件变量以使前面那个例子运行得更有效率:
· thread_function中的循环检查标志。如果标志没有被设置则线程开始等待条件变量。
· set_thread_flag 函数在改变了标志的值之后向条件变量发送信号。这样,如果
thread_function 处于等待条件变量的状态,则它会恢复运行并重新检查标志。
这里有一个问题:检查状态的操作与对条件变量进行的等待或发送信号操作之间可能形
成竞争状态。假设thread_function 检查了标志,发现标志没有被设置。这时候,Linux 调
度器暂停了这条线程而返回运行主线程。很偶然的,主线程正处于set_thraed_flag 中。它
设置了标志,然后向条件变量发送了信号。因为这个时候没有线程在等待这个条件变量的信
号(别忘了,thread_function在开始等待信号量上的事件之前就被暂停了执行),这个信号  |为什么不能检测到刚刚setd的信号量了???
就此丢失了。现在,Linux 重新调度并回到原先的线程,这个线程开始等待信号并很可能会
永远等待下去。
 

This is how you would use a condition variable to make the previous sample more
efficient:
n The loop in thread_functionchecks the flag. If the flag is not set, the thread
waits on the condition variable.
n The set_thread_flagfunction signals the condition variable after changing the
flag value.That way, if thread_functionis blocked on the condition variable, it
will be unblocked and will check the condition again.
There’s one problem with this:There’s a race condition between checking the
flag value and signaling or waiting on the condition variable. Suppose that
thread_functionchecked the flag and found that it was not set. At that moment, the
Linux scheduler paused that thread and resumed the main one. By some coincidence,
the main thread is in set_thread_flag. It sets the flag and then signals the condition
variable. Because no thread is waiting on the condition variable at the time (remember
that thread_functionwas paused before it could wait on the condition variable), the
signal is lost. Now, when Linux reschedules the other thread, it starts waiting on the
condition variable and may end up blocked forever.

To solve this problem, we need a way to lock the flag and the condition variable
together with a single mutex. Fortunately, GNU/Linux provides exactly this mechanism.

Each condition variable must be used in conjunction with a mutex, to prevent
this sort of race condition. Using this scheme (组合), the thread function follows these steps:
1. The loop in thread_function locks the mutex and reads the flag value.
2. If the flag is set, it unlocks the mutex and executes the work function.
3. If the flag is not set, it atomically unlocks the mutex and waits on the condition
variable.
The critical feature here is in step 3, in which GNU/Linux allows you to unlock the
mutex and wait on the condition variable atomically, without the possibility of
another thread intervening.This eliminates(消除) the possibility that another thread may
change the flag value and signal the condition variable in between thread_function’s
test of the flag value and wait on the condition variable.

A condition variable is represented by an instance of pthread_cond_t. Remember
that each condition variable should be accompanied by a mutex.These are the functions that manipulate condition variables:
[] pthread_cond_init initializes a condition variable.The first argument is a
pointer to a pthread_cond_tinstance.The second argument, a pointer to a con-dition variable attribute object, is ignored under GNU/Linux.
The mutex must be initialized separately, as described in Section 4.4.2,
“Mutexes.”
[]pthread_cond_signal signals a condition variable. A single thread that is blocked
on the condition variable will be unblocked. If no other thread is blocked on
the condition variable, the signal is ignored.The argument is a pointer to the
pthread_cond_tinstance.
A similar call,pthread_cond_broadcast, unblocks allthreads that are blocked on
the condition variable, instead of just one.
[]pthread_cond_wait blocks the calling thread until the condition variable is signaled.

The argument is a pointer to the pthread_cond_tinstance.The second
argument is a pointer to the pthread_mutex_tmutex instance.
When pthread_cond_waitis called, the mutex must already be locked by the
calling thread.That function atomically unlocks the mutex and blocks on the
condition variable.When the condition variable is signaled and the calling thread
unblocks,pthread_cond_wait automatically reacquires a lock on the mutex.
Whenever your program performs an action that may change the sense of the condition

you’re protecting with the condition variable, it should perform these steps. (In
our example, the condition is the state of the thread flag, so these steps must be taken
whenever the flag is changed.)
1. Lock the mutex accompanying the condition variable.
2. Take the action that may change the sense of the condition (in our example, set
the flag).
3. Signal or broadcast the condition variable, depending on the desired behavior.
4. Unlock the mutex accompanying the condition variable.
Listing 4.14 shows the previous example again, now using a condition variable to
protect the thread flag. Note that in thread_function, a lock on the mutex is held
before checking the value of thread_flag.That lock is automatically released by
pthread_cond_wait before blocking and is automatically reacquired afterward. Also
note that set_thread_flaglocks the mutex before setting the value of thread_flag
and signaling the mutex.

Listing 4.14 (condvar.c) Control a Thread Using a Condition Variable
#include <pthread.h>
int thread_flag;
pthread_cond_t thread_flag_cv;
pthread_mutex_t thread_flag_mutex;
void initialize_flag ()
{
/* Initialize the mutex and condition variable. */
pthread_mutex_init (&thread_flag_mutex, NULL);
pthread_cond_init (&thread_flag_cv, NULL);
/* Initialize the flag value. */
thread_flag = 0;
}
/* Calls do_work repeatedly while the thread flag is set; blocks if
the flag is clear. */
void* thread_function (void* thread_arg)
{
/* Loop infinitely. */
while (1) {
/* Lock the mutex before accessing the flag value. */
pthread_mutex_lock (&thread_flag_mutex);
while (!thread_flag)
/* The flag is clear. Wait for a signal on the condition
variable, indicating that the flag value has changed. When the
signal arrives and this thread unblocks, loop and check the
flag again. */
pthread_cond_wait (&thread_flag_cv, &thread_flag_mutex);
/* When we’ve gotten here, we know the flag must be set. Unlock
the mutex. */
pthread_mutex_unlock (&thread_flag_mutex);
/* Do some work. */
do_work ();
}
return NULL;
}
/* Sets the value of the thread flag to FLAG_VALUE. */
void set_thread_flag (int flag_value)
{
/* Lock the mutex before accessing the flag value. */
pthread_mutex_lock (&thread_flag_mutex);
/* Set the flag value, and then signal in case thread_function is
blocked, waiting for the flag to become set. However,
thread_function can’t actually check the flag until the mutex is
unlocked. */
91 4.4 Synchronization and Critical Sections
thread_flag = flag_value;
pthread_cond_signal (&thread_flag_cv);
/* Unlock the mutex. */
pthread_mutex_unlock (&thread_flag_mutex);
}
The condition protected by a condition variable can be arbitrarily complex. However,
before performing any operation that may change the sense of the condition, a mutex
lock should be required, and the condition variable should be signaled afterward.
A condition variable may also be used without a condition, simply as a mechanism
for blocking a thread until another thread “wakes it up.”A semaphore may also be
used for that purpose.The principal difference is that a semaphore “remembers”the
wake-up call even if no thread was blocked on it at the time, while a condition
variable discards the wake-up call unless some thread is actually blocked on it
at the time. Also, a semaphore delivers only a single wake-up per post; with
pthread_cond_broadcast, an arbitrary and unknown number of blocked threads
may be awoken at the same time.

原文地址:https://www.cnblogs.com/michile/p/2891144.html