前言 Linux 平台下, 线程等待和唤醒操作是很常见的, 但是平台函数不易使用; 笔者对此操作做了封装, 使之更易于使用.
线程等待和唤醒函数比较
平台提供了线程等待相关函数, 这些函数之间用法也有些差异:
sleep | 线程等待,等待期间线程无法唤醒。 |
pthread_cond_wait | 线程等待信号触发,如果没有信号触发,无限期等待下去。 |
pthread_cond_timedwait | 线程等待一定的时间,如果超时或有信号触发,线程唤醒。 |
通过上表, 可以看出 pthread_cond_timedwait 函数是最为灵活, 使用也最为广泛. sleep 的缺陷是当有紧急事件到达时, 线程无法及时唤醒. pthread_cond_wait 缺陷是: 必须借助别的线程触发信号, 否则线程自身无法唤醒, 如果使用函数, 线程无法处理定时任务.
一般情况下, 线程要做的工作可能有: 定期处理某个事物; 无事可做时, 线程挂起; 有事可做时, 立即唤醒工作. 要完成上面所述的功能, 必须用 pthread_cond_timedwait 函数, 本文介绍的就是对该函数封装.
线程唤醒操作还涉及互斥量 pthread_mutex_t, 感觉与我们理解的等待和唤醒操作无关; 此函数的引入, 增加了理解难度. 本文封装完全屏蔽了此概念.
函数定义如下
- // 函数涉及的变量
- typedef struct ThreadSignal_T
- {
- BOOL relativeTimespan; // 是否采用相对时间
- pthread_cond_t cond;
- pthread_mutex_t mutex;
- pthread_condattr_t cattr;
- } ThreadSignal;
- // 初始化
- void ThreadSignal_Init(ThreadSignal *signal,BOOL relativeTimespan);
- // 关闭
- void ThreadSignal_Close(ThreadSignal *signal);
- // 等待 n 毫秒
- void ThreadSignal_Wait(ThreadSignal *signal, int ms);
- // 唤醒线程
- void ThreadSignal_Signal(ThreadSignal *signal);
上述函数定义非常直观, 利于理解. 但是平台提供的函数, 就不是那么直观. 我把上述函数的实现一一列出来.
- 1)ThreadSignal_Init
- void ThreadSignal_Init(ThreadSignal *signal, BOOL relativeTimespan)
- {
- //relativeTimespan 是不是采用相对时间等待. 参见函数 ThreadSignal_Wait
- signal->relativeTimespan = relativeTimespan;
- pthread_mutex_init(&signal->mutex, NULL);
- if (relativeTimespan)
- {
- // 如果采用相对时间等待, 需要额外的处理.
- // 采用相对时间等待. 可以避免: 因系统调整时间, 导致等待时间出现错误.
- int ret = pthread_condattr_init(&signal->cattr);
- ret = pthread_condattr_setclock(&signal->cattr, CLOCK_MONOTONIC);
- ret = pthread_cond_init(&signal->cond, &signal->cattr);
- }
- else
- {
- pthread_cond_init(&signal->cond, NULL);
- }
- }
- 2) ThreadSignal_Close
- void ThreadSignal_Close(ThreadSignal *signal)
- {
- if (signal->relativeTimespan)
- {
- pthread_condattr_destroy(&(signal->cattr));
- }
- pthread_mutex_destroy(&signal->mutex);
- pthread_cond_destroy(&signal->cond);
- }
- 3) ThreadSignal_Wait
- void ThreadSignal_Wait(ThreadSignal *signal, int ms)
- {
- pthread_mutex_lock(&signal->mutex);
- if (signal->relativeTimespan)
- {
- // 获取时间
- struct timespec outtime;
- clock_gettime(CLOCK_MONOTONIC, &outtime);
- //ms 为毫秒, 换算成秒
- outtime.tv_sec += ms/1000;
- // 在 outtime 的基础上, 增加 ms 毫秒
- //outtime.tv_nsec 为纳秒, 1 微秒 = 1000 纳秒
- //tv_nsec 此值再加上剩余的毫秒数 ms%1000, 有可能超过 1 秒. 需要特殊处理
- uint64_t us = outtime.tv_nsec/1000 + 1000 * (ms % 1000); // 微秒
- //us 的值有可能超过 1 秒,
- outtime.tv_sec += us / 1000000;
- us = us % 1000000;
- outtime.tv_nsec = us * 1000;// 换算成纳秒
- int ret = pthread_cond_timedwait(&signal->cond, &signal->mutex, &outtime);
- }
- else
- {
- struct timeval now;
- gettimeofday(&now, NULL);
- // 在 now 基础上, 增加 ms 毫秒
- struct timespec outtime;
- outtime.tv_sec = now.tv_sec + ms / 1000;
- //us 的值有可能超过 1 秒
- uint64_t us = now.tv_usec + 1000 * (ms % 1000);
- outtime.tv_sec += us / 1000000;
- us = us % 1000000;
- outtime.tv_nsec = us * 1000;
- int ret = pthread_cond_timedwait(&signal->cond, &signal->mutex, &outtime);
- }
- pthread_mutex_unlock(&signal->mutex);
- }
上述函数处理起来有点啰嗦, 有些读者可能认为这是多此一举, 其实不然. struct timespec outtime; 结构中有两个值: tv_sec ,tv_usec . 分别是秒和纳秒. 等待一段时间就是: 在这两个值上增加一定的数值.
tv_usec 此值有范围限制的, 就是不能超过 1 秒暨 1000000000 纳秒. 如果超出 1 秒, 就要在 tv_sec 此值增加一秒; tv_usec 减去一秒. 笔者是在实践中发现此问题的, 不是无中生有. 如果 tv_usec 此值溢出, 调用 pthread_cond_timedwait 函数, 会立马返回.
- 4)ThreadSignal_Signal
- void ThreadSignal_Signal(ThreadSignal *signal)
- {
- pthread_mutex_lock(&signal->mutex);
- pthread_cond_signal(&signal->cond);
- pthread_mutex_unlock(&signal->mutex);
- }
后记 一个简单的信号等待操作, Linux 下处理起来就如此复杂, 远不如 c#,java 等语言处理起来那么优雅. 通过本文介绍的方法, 将复杂的问题封装起来, 让使用者用起来得心应手, 这就是函数封装要达到的目的.
来源: https://www.cnblogs.com/yuanchenhui/p/thread_signal.html