一:基础知识
(1)1us = 1000ns
(2)
struct timespec
{
time_t tv_sec; // 秒
long tv_nsec; // 纳秒1000,000,000分之一秒
};
(3)
struct timeval
{
time_t tv_sec; // 秒
long tv_usec; //微秒
};
(4)一些宏和系统函数接口
CLOCK_REALTIME 统当前时间,从1970年1.1日算起
CLOCK_MONOTONIC 系统的启动时间,不能被设置
CLOCK_PROCESS_CPUTIME_ID 本进程运行时间
CLOCK_THREAD_CPUTIME_ID 本线程运行时间
struct tm *localtime(const time_t *clock); //线程不安全
struct tm* localtime_r( const time_t* timer, struct tm* result );//线程安全
size_t strftime (char* ptr, size_t maxsize, const char* format, const struct tm* timeptr );
//以上为预备知识,先有个大概认识。
二:概述
(1)条件变量也是一种线程同步机制:
条件变量与互斥量不同,互斥量是防止多线程同时访问共享的互斥变量来保护临界区。
条件变量是多线程间可以通过它来告知其他线程某个状态发生了改变,让等待在这个条件变量的线程继续执行。通俗一点来讲:设置一个条件变量让线程1,等待在一个临界区的前面,当其他线程给这个变量执行通知操作时,线程1才会被唤醒,
继续向下执行。条件变量总是和互斥量一起使用,互斥量保护着条件变量,防止多个线程对条件变量产生竞争。
(2)系统接口
二.函数接口
1.初始化条件变量
宏常量初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
函数初始化
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
跟互斥量类似,cond是条件变量的结构指针,attr是条件变量属性的结构指针。
2.等待和通知条件变量
#include <pthread.h>
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
等待函数里面,要传入一个互斥量。pthread_cond_timewait()可以指定一个时间来等待,
如果规定的时间没有获得通知,就返回ETIMEDOUT错误。而pthread_cond_wait()会一直阻塞。
通知函数,pthread_cond_signal()至少唤醒一个等待的线程,pthread_cond_broadcast()会唤醒在该条件变量上所有线程。
3.销毁条件变量
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
范例一:看看条件变量与互斥锁的配套使用
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
/* 定义互斥量 */
pthread_mutex_t mtx;
/* 互斥量属性 */
pthread_mutexattr_t mtx_attr;
/* 全局资源 */
int money;
/* 条件变量 */
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void err_exit(const char *err_msg)
{
printf("error:%s\n", err_msg);
exit(1);
}
/* 线程函数 */
void *thread_fun(void *arg)
{
while (1)
{
/* 加锁 */
pthread_mutex_lock(&mtx);
/* 条件变量 */
while (money > 0)
{
printf("子线程坐等money等于0...\n");
pthread_cond_wait(&cond, &mtx);
}
printf("子线程进入临界区查看money\n");
if (money == 0)
{
money += 200;
printf("子线程:money = %d\n", money);
}
/* 解锁 */
pthread_mutex_unlock(&mtx);
sleep(1);
}
return NULL;
}
int main(void)
{
pthread_t tid;
/* 初始化互斥量属性 */
if (pthread_mutexattr_init(&mtx_attr) == -1)
err_exit("pthread_mutexattr_init()");
/* 设置互斥量属性 */
if (pthread_mutexattr_settype(&mtx_attr, PTHREAD_MUTEX_NORMAL) == -1)
err_exit("pthread_mutexattr_settype()");
/* 初始化互斥量 */
if (pthread_mutex_init(&mtx, &mtx_attr) == -1)
err_exit("pthread_mutex_init()");
/* 创建一个线程 */
if (pthread_create(&tid, NULL, thread_fun, NULL)== -1)
err_exit("pthread_create()");
money = 1000;
while (1)
{
/* 加锁 */
pthread_mutex_lock(&mtx);
if (money > 0)
{
money -= 100;
printf("主线程:money = %d\n", money);
}
/* 解锁 */
pthread_mutex_unlock(&mtx);
/* 如果money = 0,就通知子线程 */
if (money == 0)
{
printf("通知子线程\n");
pthread_cond_signal(&cond);
}
sleep(1);
}
return 0;
}
/*
(1)代码执行到pthread_cond_wait时,
发生的步骤,解锁->等待->上锁(函数成功返回时会对互斥量进行上锁,这样才能保证同步)
*/
范例二:看看pthread_cond_timedwait的使用
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <semaphore.h>
#include <sys/time.h>
#define SENDSIGTIME 100
pthread_cond_t g_cond;
pthread_mutex_t g_mutex;
void thread1(void *arg)
{
int inArg = (int)arg;
int ret = 0;
struct timeval now;
struct timespec outtime;
pthread_mutex_lock(&g_mutex);
/*这个是系统接口函数,获取系统的时间,精确到微秒
举个例子,比如获取到的时间是,2021/3/6 14:28分 30秒,1微秒*/
gettimeofday(&now, NULL);
/*加上30,代表我想做一些超时处理,如果30秒内没有收到信号,那么函数也会返回成功*/
outtime.tv_sec = now.tv_sec + 30;
/*这里因为要传入的实参是一个结构体,一个是秒,另一个是纳秒,
但是,我们利用gettimeofday函数获取的是秒和微秒,所以要做一个转换,
秒的直接像上一步程序那样处理就ok,但是,这里要把获取到的微秒转换成纳秒
所以,乘以1000,这样outtime结构体变量才能作为实参传入pthread_cond_timedwait函数*/
outtime.tv_nsec = now.tv_usec * 1000;
ret = pthread_cond_timedwait(&g_cond, &g_mutex, &outtime);
//ret = pthread_cond_wait(&g_cond, &g_mutex);
pthread_mutex_unlock(&g_mutex);
printf("thread 1 ret: %d\n", ret);
}
int main(void)
{
pthread_t id1;
int ret;
pthread_cond_init(&g_cond, NULL);
pthread_mutex_init(&g_mutex, NULL);
ret = pthread_create(&id1, NULL, (void *)thread1, (void *)1);
if (0 != ret)
{
printf("thread 1 create failed!\n");
return 1;
}
/*本来我们想休眠100s,再发送信号,但是,由于做了超时处理,所以,30秒以后
pthread_cond_timedwait函数可以返回成功,接下来,线程1就会运行结束,
等sleep时间运行结束以后,,程序往下执行,主线程也随之运行结束*/
printf("等待%ds发送信号!\n", SENDSIGTIME);
sleep(SENDSIGTIME);
printf("正在发送信号....\n");
pthread_mutex_lock(&g_mutex);
pthread_cond_signal(&g_cond);
pthread_mutex_unlock(&g_mutex);
pthread_join(id1, NULL);
pthread_cond_destroy(&g_cond);
pthread_mutex_destroy(&g_mutex);
return 0;
}
//在定时时间内返回时,返回值为0.
范例三:来看看pthread_cond_broadcast和pthread_cond_signal的区别
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#define MAX_THREAD_NUM 5
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void* thread_fun(void* arg)
{
/*打印是线程几*/
int index = *(int*)arg;
printf("[%d]thread start up!\n", index);
/*一定要注意pthread_cond_wait,一定要和lock,unlock 配套使用*/
/*上锁*/
pthread_mutex_lock(&mutex);
printf("[%d]thread wait...\n", index);
pthread_cond_wait(&cond, &mutex); //解锁->等待->解锁
/*查看是线程几被唤醒*/
printf("[%d]thread wake up\n", index);
/*解锁*/
pthread_mutex_unlock(&mutex);
/*最后退出线程*/
pthread_exit(0);
}
int main()
{
pthread_t tid[MAX_THREAD_NUM];
/*创建5个线程*/
for(int i = 0; i < MAX_THREAD_NUM; i++)
{
pthread_create(&tid[i], NULL, thread_fun, &i);
sleep(1);
}
/*先确保所有线程已经创建成功并执行到pthread_cond_wait函数*/
sleep(10);
/*只会唤醒一个线程,pthread_cond_signal不必和lock,unlock一起配套使用*/
pthread_cond_signal(&cond);
/*所有线程都会被唤醒*/
//pthread_cond_broadcast(&cond); //全部唤醒
/*阻塞所有线程*/
for(int i = 0; i < MAX_THREAD_NUM; ++i)
{
pthread_join(tid[i], NULL);
}
return 0;
}
该图为使用pthread_cond_signal运行的结果,只有一个线程被唤醒。具体是那个线程,要看系统,如果有些线程设置了优先级高的属性,那么可能是这些线程优先被唤醒。
该图为使用pthread_cond_broadcast运行结果。可以看到,所有线程都被唤醒。