欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 金融 > linux用户态条件变量和内核态完成变量

linux用户态条件变量和内核态完成变量

2024/10/26 2:31:27 来源:https://blog.csdn.net/weixin_38184628/article/details/142011897  浏览:    关键词:linux用户态条件变量和内核态完成变量

如果我们的线程要等一个条件满足之后才可以继续向下执行,这个条件不满足的话,就要等待这个条件。这种场景经常见到,比如我们使用recv接收网络数据的时候,或者使用epoll_wait来等待事件的时候,在默认情况下,recv和epoll_wait都会等到有数据或者事件到来才会返回。

生产者和消费者的场景是条件变量典型的应用场景。一个队列,一个生产者,一个消费者,生产者向队列中生产数据,消费者从队列中消费数据。当队列满的时候,生产者就不能继续生产了,需要等有元素被消费之后才能继续生产;当队列空的时候,消费者不能继续消费数据了,需要有新的数据被生产才能继续消费。

具体怎么等待条件呢?有两种基本的方式:

轮询:

比如生产者在队列满的时候,通过轮询的方式不断地去确定队列是不是满,如果满,则继续等待;否则就继续生产数据。

条件:

当队列满的时候,生产者就睡眠,消费者消费数据之后,将生产者唤醒。

轮询和条件是两种基本的实现方式,这两种思想应用于很多场景,比如硬件和软件的通信方式:轮询和中断,中断就类似于这里的条件;比如多线程中使用的自旋锁和互斥体,前者是轮询的方式,后者是条件的方式。

本文讨论条件的方式。在linux中用户态和内核态均提供了这样的机制:用户态的条件变量,内核态的完成变量。

1用户态:条件变量

1.1使用

(1)条件变量要和互斥体mutex在一块使用

(2)wait的时候有两点需要注意

①wait之前要加锁,wait的时候会释放锁,wait返回之前会再次加锁。也就是说在函数pthread_cond_wait中,会有一次解锁和加锁的过程。

②wait返回之后要再次判断条件是不是满足,本例中消费者和生产者只有一个,而在实际使用中,生产者和消费者可能有多个。如果唤醒侧调用了pthread_cond_broadcast,那么等待者就会全部被唤醒,这样可能数据已经被先唤醒的线程消费了,后被唤醒的线程没有数据消费,所以需要加判断。这是条件变量的典型用法。

(3)wait侧使用方式

加锁

wait

解锁

(4)唤醒侧使用方式

加锁

signal/broadcast

解锁

唤醒侧其实也可以不加锁,也能起到唤醒的作用,如下man手册中所说。但是如果要求行为是可预知的,那么最好加锁。在实际应用中,我们当然需要代码的行为是可预知的,所以我们在使用中要加锁。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>#define BUFFER_SIZE 5int buffer[BUFFER_SIZE];
int count = 0;pthread_mutex_t mutex;
pthread_cond_t cond_producer;
pthread_cond_t cond_consumer;void* producer(void* arg) {for (int i = 0; i < 10; i++) {pthread_mutex_lock(&mutex);while (count == BUFFER_SIZE) {pthread_cond_wait(&cond_producer, &mutex);}buffer[count] = i;count++;printf("Produced: %d\n", i);pthread_cond_signal(&cond_consumer);pthread_mutex_unlock(&mutex);sleep(1);}return NULL;
}void* consumer(void* arg) {for (int i = 0; i < 10; i++) {pthread_mutex_lock(&mutex);while (count == 0) {pthread_cond_wait(&cond_consumer, &mutex);}count--;int item = buffer[count];printf("Consumed: %d\n", item);pthread_cond_signal(&cond_producer);pthread_mutex_unlock(&mutex);sleep(2);}return NULL;
}int main() {pthread_t prod_thread, cons_thread;pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond_producer, NULL);pthread_cond_init(&cond_consumer, NULL);pthread_create(&prod_thread, NULL, producer, NULL);pthread_create(&cons_thread, NULL, consumer, NULL);pthread_join(prod_thread, NULL);pthread_join(cons_thread, NULL);pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond_producer);pthread_cond_destroy(&cond_consumer);return 0;
}

1.2惊群问题

惊群问题说的是,如果有多个线程wait阻塞,这个时候一个线程调用signal,可能会唤醒多个线程。如下man手册中也说的是,pthread_cond_signal会唤醒至少一个线程,也就是说至少一个,也可能大于一个,大于一个就是惊群(你只扔了一个米粒,导致鸡群都跑了过来,但是只有一只鸡能够吃到米粒,其它的米都白跑一趟,惊动集群)

本人在在linux环境下测试,从来没有出现过惊群问题。即使有惊群问题,通过如下典型的使用方式,在wait返回之后再次判断,也能避免惊群问题引入其它问题。

while (count == 0) {pthread_cond_wait(&cond_consumer, &mutex);
}

1.3destroy无法返回问题

如果一个条件变量,有线程在wait这个条件变量。这个时候调用pthread_cond_destroy销毁条件变量,那么pthread_cond_destroy是会阻塞住,直到pthread_cond_wait返回之后,destroy才会成功。

在实际应用中,在类的析构函数里,或者进程的main函数返回时,需要销毁资源,往往会调用pthread_cond_destroy销毁条件变量,此时就要注意是不是有线程在wait。如果遇到destroy不返回的情况,要排查是不是有线程wait的情况。也可以使用pthread_cond_timedwait这个api,这个api不会一直等待,如果条件不满足,超时的时候,也会返回。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>pthread_mutex_t mutex;
pthread_cond_t cond_var;void *wait_thread(void *arg) {pthread_mutex_lock(&mutex);struct timespec ts;clock_gettime(CLOCK_REALTIME, &ts);ts.tv_sec += 5;pthread_cond_timedwait(&cond_var, &mutex, &ts);// pthread_cond_wait(&cond_var, &mutex);printf("Wait thread resumed.\n");pthread_mutex_unlock(&mutex);return NULL;
}void *destroy_thread(void *arg) {printf("before destroy condition.\n");pthread_cond_destroy(&cond_var);printf("after destroy condition.\n");return NULL;
}int main() {pthread_t wait_tid, destroy_tid;pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond_var, NULL);pthread_create(&wait_tid, NULL, wait_thread, NULL);sleep(1);pthread_create(&destroy_tid, NULL, destroy_thread, NULL);pthread_join(wait_tid, NULL);pthread_join(destroy_tid, NULL);pthread_mutex_destroy(&mutex);return 0;
}

2内核态:完成变量

如下是内核完成变量使用的例子。

struct completion声明一个完成变量,init_completion初始化完成变量,wait_for_completion等待完成,complete唤醒等待线程。另外,complete_all可以唤醒所有的等待线程。

用户态条件变量内核态完成变量
pthread_cont_tstruct completion
pthread_cond_initinit_completion
pthread_cond_waitwait_for_completion
pthread_cond_signalcomplete
pthread_cond_broadcastcomplete_all

源码:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/kthread.h>MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple completion example module");static struct completion my_completion;
static struct task_struct *my_thread;static int my_thread_fn(void *data)
{printk(KERN_INFO "Thread is sleeping for 5 seconds...\n");ssleep(5);printk(KERN_INFO "Thread is completing...\n");complete(&my_completion);return 0;
}static int __init my_module_init(void)
{printk(KERN_INFO "Loading my completion module...\n");init_completion(&my_completion);my_thread = kthread_run(my_thread_fn, NULL, "my_thread");if (IS_ERR(my_thread)) {printk(KERN_ERR "Failed to create thread\n");return PTR_ERR(my_thread);}wait_for_completion(&my_completion);printk(KERN_INFO "Thread has completed!\n");return 0;
}static void __exit my_module_exit(void)
{printk(KERN_INFO "Unloading my completion module...\n");
}module_init(my_module_init);
module_exit(my_module_exit);

Makefile:

obj-m += comp.o
all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com