共计 3119 个字符,预计需要花费 8 分钟才能阅读完成。
条件变量
条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待”条件变量的条件成立”而挂起;另一个线程使”条件成立”(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
条件变量的创建与注销
条件变量的创建也是有两种方式:静态方式与动态方式。
条件变量的数据结构如下:
typedef union
{
struct
{
int __lock;
unsigned int __futex;
__extension__ unsigned long long int __total_seq;
__extension__ unsigned long long int __wakeup_seq;
__extension__ unsigned long long int __woken_seq;
void *__mutex;
unsigned int __nwaiters;
unsigned int __broadcast_seq;
} __data;
char __size[__SIZEOF_PTHREAD_COND_T];
__extension__ long long int __align;
} pthread_cond_t;
静态方式:与互斥量一样也存在默认条件变量PTHREAD_COND_INITIALIZER 常量
pthread_cond_t cond=PTHREAD_COND_INITIALIZER
动态方式:通过调用API函数pthread_cond_init函数,函数原型如下:
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)
创建成功会返回0,失败则返回错误代码。尽管 POSIX 标准中为条件变量定义了属性,但在 LinuxThreads 中没有实现,因此 cond_attr值通常为 NULL,且被忽略。
线程注销:API函数原型如下:
int pthread_cond_destroy(pthread_cond_t *cond);
pthread_cond_destroy函数可以用来摧毁所指定的条件变量,同时将会释放所给它分配的资源。调用该函数的进程也并不要求等待在参数所指定的条件变量上。因为 Linux 实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程。
线程的等待与激活
线程等待
线程等待API提供了两个API函数:
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)
注释:前两个参数分别是指向条件变量的指针和指向互斥量的指针,第二个函数的第三个变量是指向timespec结构体的指针,意思是为等待的线程提供倒计时,若时间到了还未被唤醒则返回ETIMEOUT,结束等待。结构体的定义如下:
struct timespec
{
__time_t tv_sec; /* Seconds. */
long int tv_nsec; /* Nanoseconds. */
};
无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求 pthread_cond_wait()(或 pthread_cond_timedwait(),下同)的竞争条件(Race Condition)。mutex 互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP)且在调用 pthread_cond_wait()前必须由本线程加锁pthread_mutex_lock(),而在更新条件等待队列以前,mutex 保持锁定状态,并在线程挂起进入等待前解锁。
线程的激活
/* Wake up one thread waiting for condition variable COND. */
extern int pthread_cond_signal (pthread_cond_t *__cond)
__THROWNL __nonnull ((1));
/* Wake up all threads waiting for condition variables COND. */
extern int pthread_cond_broadcast (pthread_cond_t *__cond)
__THROWNL __nonnull ((1));
注释:二者区别在于第一个只会一次唤醒一个线程但是第二个会唤醒所有等待线程。
其他
pthread_cond_wait()和 pthread_cond_timedwait()都被实现为取消点,因此,在该处等待的线程将立即重 新运行,在重新锁定 mutex 后离开 pthread_cond_wait(),然后执行取消动作。也就是说如果 pthread_cond_wait()被 取消,mutex 是保持锁定状态的,因而需要定义退出回调函数来为其解锁。
条件变量与互斥量例子解析
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;/*初始化互斥锁*/
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;/*初始化条件变量*/
void *thread1(void *);
void *thread2(void *);
int i=1;
int main(void)
{
pthread_t t_a;
pthread_t t_b;
pthread_create(&t_a,NULL,thread2,(void *)NULL);/*创建进程t_a*/
pthread_create(&t_b,NULL,thread1,(void *)NULL); /*创建进程t_b*/
pthread_join(t_b, NULL);/*等待进程t_b结束*/
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
exit(0);
}
void *thread1(void *junk)
{
for(i=1;i<=9;i++)
{
pthread_mutex_lock(&mutex);/*锁住互斥量*/
if(i%3==0)
pthread_cond_signal(&cond);/*条件改变,发送信号,通知t_b进程*/
else
printf("thead1:%d\n",i);
pthread_mutex_unlock(&mutex);/*解锁互斥量*/
sleep(1);
}
}
void *thread2(void *junk)
{
while(i<9)
{
pthread_mutex_lock(&mutex);
if(i%3!=0)
pthread_cond_wait(&cond,&mutex);/*等待*/
printf("thread2:%d\n",i);
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
注释:主线程在运行到pthread_jion时被挂起直到子线程运行结束,子线程t_b运行但是只有遇到数字等于3的倍数时才会输出,所以第一次在遇到全局变量i不等于3倍数时使用
使用pthread_cond_wat使其阻塞,线程t_a继续运行,如此循环输出直到子线程运行结束实现线程的同步!
运行结果截图: