目录
相关API
restrict关键字
线程间条件切换函数
条件变量pthread_cond_t
案例
在前面的锁的基础上进一步提高线程同步效率,也就是两个线程只用锁去执行的话依然会存在资源竞争的情况,也就是抢锁,这里就需要在锁的这边加上限制,也就是条件变量,有了条件变量的加持,就可以很好的控制线程与线程之间的协同。
相关API
restrict关键字
restrict是一个C99标准引入的关键字,用于修饰指针,它的作用是告诉编译器,被修饰的指针是编译器所知的唯一一个可以在其作用域内用来访问指针所指向的对象的方法。这样一来,编译器可以放心地执行代码优化,因为不存在其他的别名(即其他指向同一内存区域的指针)会影响到这块内存的状态。
restrict声明了一种约定,主要目的是允许编译器在生成代码时做出优化假设,而不是在程序的不同部分间强制执行内存访问的规则。程序员需要确保遵守restrict的约定,编译器则依赖这个约定来进行优化。如果restrict约定被违反,可能导致未定义行为。
函数参数使用restrict修饰,相当于约定:函数执行期间,该参数指向的内存区域不会被其它指针修改。
线程间条件切换函数
如果需要两个线程协同工作,可以使用条件变量完成线程切换。查看文档可得:
#include <pthread.h>
/**
* @brief 调用该方法的线程必须持有mutex锁。调用该方法的线程会阻塞并临时释放mutex锁,并等待其他线程调用pthread_cond_signal或pthread_cond_broadcast唤醒。被唤醒后该线程会尝试重新获取mutex锁。
*
* @param cond 指向条件变量的指针。条件变量用于等待某个条件的发生。通过某一cond等待的线程需要通过同一cond的signal唤醒
* @param mutex 与条件变量配合使用的互斥锁的指针。在调用pthread_cond_wait之前,线程必须已经获得了这个互斥锁。
* @return int 成功时返回0;失败时返回错误码,而非-1。错误码可能包括EINVAL、EPERM等,具体取决于错误的性质。
*/
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
/**
* @brief 同pthread_cond_wait相似,但是它添加了超时机制。如果在指定的abstime时间内条件变量没有被触发,函数将返回一个超时错误(ETIMEDOUT)。
*
* @param cond 指向条件变量的指针
* @param mutex 与条件变量配合使用的互斥锁的指针
* @param abstime 指向timespec结构的指针,表示等待条件变量的绝对超时时间。timespec结构包含秒和纳秒两部分,指定了从某一固定点(如UNIX纪元,1970年1月1日)开始的时间。
* @return int 成功时返回0;如果超时则返回ETIMEDOUT;其他错误情况返回相应的错误码。
*/
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
/**
* @brief 唤醒因cond而阻塞的线程,如果有多个线程因为cond阻塞,那么随机唤醒一个。如果没有线程在等待,这个函数什么也不做。
*
* @param cond 指向条件变量的指针
* @return int 成功时返回0;失败时返回错误码
*/
int pthread_cond_signal(pthread_cond_t *cond);
/**
* @brief 唤醒所有正在等待条件变量cond的线程。如果没有线程在等待,这个函数什么也不做。
*
* @param cond 指向条件变量的指针。
* @return int 成功时返回0;失败时返回错误码。
*/
int pthread_cond_broadcast(pthread_cond_t *cond);
- 使用条件变量时,通常涉及到一个或多个线程等待“条件变量”代表的条件成立,而另外一些线程在条件成立时触发条件变量。
- 条件变量的使用必须与互斥锁配合,以保证对共享资源的访问是互斥的。
- 条件变量提供了一种线程间的通信机制,允许线程以无竞争的方式等待特定条件的发生。
条件变量pthread_cond_t
pthread_cond_t是一个条件变量,它是线程间同步的另一种机制。与pthread_mutex_t相同,它也定义在头文件<pthreadtypes.h>中,其声明如下。
typedef union
{
struct __pthread_cond_s __data;
char __size[__SIZEOF_PTHREAD_COND_T];
__extension__ long long int __align;
} pthread_cond_t;
案例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
// 同步队列?
#define BUFFER_SIZE 10
int buffer[BUFFER_SIZE];
int count = 0;
// 互斥锁
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 初始化调解笔录
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
// 生产者线程函数
void* producer_fun(void* args) {
int i = 1;
while (1)
{
// 使用共同变量,使用互斥锁
pthread_mutex_lock(&mutex);
// 如果缓冲区慢了就要暂停放入
if (count == BUFFER_SIZE) {
pthread_cond_wait(&cond, &mutex);
}
printf("生产者写入%d\n", i);
buffer[count++] = i++;
// 唤醒消费者
pthread_cond_signal(&cond);
// 最后释放锁
pthread_mutex_unlock (&mutex);
}
}
void* consumer_fun(void* args) {
while (1)
{
pthread_mutex_lock(&mutex);
// 数据读完了
if (count == 0) {
pthread_cond_wait(&cond, &mutex);
}
printf("消费者收到数据%d\n", buffer[--count]);
// 唤醒生产者
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
}
int main(int argc, char const *argv[])
{
pthread_t producer, consumer;
// 创建线程
pthread_create(&producer, NULL, producer_fun, NULL);
pthread_create(&consumer, NULL, consumer_fun, NULL);
// 主线程要挂起等待子线程
pthread_join(producer, NULL);
pthread_join(consumer, NULL);
return 0;
}
上面这个写法还是会存在资源竞争的情况,也就是抢锁,实际上并不是生产者生产后消费者马上执行,而是互相竞争的。下面是修改后:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
// 同步队列?
#define BUFFER_SIZE 10
int buffer[BUFFER_SIZE];
int count = 0;
// 互斥锁
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 初始化调解笔录
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
// 生产者线程函数
void* producer_fun(void* args) {
int i = 1;
// 使用共同变量,使用互斥锁
pthread_mutex_lock(&mutex);
while (1)
{
// 如果缓冲区慢了就要暂停放入
if (count == BUFFER_SIZE) {
pthread_cond_wait(&cond, &mutex);
}
printf("生产者写入%d\n", i);
buffer[count++] = i++;
// 唤醒消费者
pthread_cond_signal(&cond);
}
// 最后释放锁
pthread_mutex_unlock (&mutex);
}
这种做法,可以实现生产者生产完后知道队列满了,然后就消费者消费直到清空,这样子一轮一轮的执行,好处如下: