说明:上一篇《 数据结构与算法 – 09 队列》介绍了队列的数据结构定义,特性,常见操作以及循环队列。理论结合实践才是硬道理,本篇收集整理一些队列的实际应用,以此加深对队列的理解。
另外,个人感觉实际应用中直接用队列的场景不多,往往是一些数据结构、功能、机制的底层嵌入了队列这一数据结构。本人经验有限,如理解有误欢迎指正^ ^。
阻塞队列
简单理解阻塞队列就是在入队和出队操作加入了阻塞操作。可能仅在入队上加,也可能仅在出队操作上加,也可能出队和入队都加,这个看具体的任务场景需要。比如:
- 入队元素太多太快,而出队很慢,这样队列很快就满了,此时就要把入队操作阻塞住,等待出队队列有空闲空间后,再恢复入队操作。
- 入队太慢,但是出队很快,导致队列很快就空了,这个时候就要把出队操作阻塞住,等待队列有新的元素后,再继续出队。
思考?这不就是操作系统经典模型–“生产者-消费者”模型嘛。
基于以上两个场景,“生产者-消费者”模型中还可以通过调节生产者线程和消费者线程数目来达到提高效率的目的:
- 入队快过出队时,通过增加出队元素的消费者线程从而加快出队的速度,进而提高资源流通效率;
- 入队慢于出队时,通过增加入队元素的生产者线程来加快入队速度,也可以提高资源流通效率;
Todo:以后遇到具体的代码应用例子再贴上来。
并发队列
多线程操作队列时会出现线程安全问题,而线程安全的队列即是并发队列。最简单的做法是在入队和出队操作上加锁。
Todo:详细的例子以后遇到了再加入进来。
Linux workqueue
首先,感谢前辈龙哥解答我对workqueue的种种疑惑,其对内核理解之深令我膜拜,是我学习的榜样。
言归正传,相信做 BSP 驱动开发维护的同学对 workqueue再熟悉不过了,应用可谓极其广泛。workqueue采用的就是链式队列。
先明确几个概念:
- work :工作,其有一个对应的回调函数对应要处理的事情。
- workqueue :工作的集合。workqueue 和 work 是一对多的关系。
- worker :工人。在代码中 worker 对应一个 work_thread() 内核线程。
- worker_pool:工人的集合。worker_pool 和 worker 是一对多的关系。
- pwq(pool_workqueue):中间人 / 中介,负责建立起 workqueue 和 worker_pool 之间的关系。workqueue 和 pwq 是一对多的关系,pwq 和 worker_pool 是一对一的关系。
下面简单说下workqueue()工作机制
-
workqueue_init() – 创建内核线程
系统初始化调用kernel_init_freeable()–>workqueue_init()时会
(1) 先给每个 CPU 分配一个 worker_pool
(2) 再为每个worker_pool 创建一个 worker thread 内核线程,并将其与 worker_pool 关联 -
alloc_workqueue() – 创建 workqueue 并找到对应的 worker_pool 进行关联
(1) init_workqueues() --> alloc_workqueue() (workqueue.c)。
系统初始化时会创建系统缺省 workqueue(总共7个,每个workqueue挂的work个数有限)来处理work,并将其与对应的worker_pool 进行关联:system_wq = alloc_workqueue("events", 0, 0); system_highpri_wq = alloc_workqueue("events_highpri", WQ_HIGHPRI, 0); system_long_wq = alloc_workqueue("events_long", 0, 0); system_unbound_wq = alloc_workqueue("events_unbound", WQ_UNBOUND, WQ_UNBOUND_MAX_ACTIVE); system_freezable_wq = alloc_workqueue("events_freezable", WQ_FREEZABLE, 0); system_power_efficient_wq = alloc_workqueue("events_power_efficient", WQ_POWER_EFFICIENT, 0); system_freezable_power_efficient_wq = alloc_workqueue("events_freezable_power_efficient", WQ_FREEZABLE | WQ_POWER_EFFICIENT, 0);
(2) chip_driver_probe()–>alloc_workqueue()。
具体的设备驱动(例如TP, sensor等)自行创建 workqueue来处理work,并将其与对应的 worker_pool 进行关联 -
queue_work() – 入队work,再调用内核线程出队work处理之
(1) 入队 work。struct worker_pool { ... struct list_head worklist; /* L: list of pending works */ ... struct list_head idle_list; /* X: list of idle workers */ } queue_work(struct workqueue_struct *wq, struct work_struct *work)
先通过workqueue 找到对应的worker_pool,再将对应的 work 入队到链式队列 worker_pool->worklist。
queue_work()-->queue_work_on()-->__queue_work()-->insert_work()-->list_add_tail()。
注:list_add_tail()即入队。
(2) 出队 work,并调用内核线程处理
调度 worker_pool->idle_list 上面的内核线程 worker thread 从worker_pool->worklist 上出队work并进行处理,真正的工作都在 work对应的回调函数work function中进行。queue_work()-->queue_work_on()-->__queue_work()-->insert_work()-->wake_up_worker()。 worker_thread()-->list_first_entry()
注:list_first_entry()即出队。
说明:这里入队和出队的不是同一个work。
思考:为何外设驱动都选择自行创建 workqueue 呢?外设驱动可以将其 work 挂靠到内核缺省 workqueue 吗?
Linux kfifo
IIO 子系统底层存储数据使用的就是 kfifo。
TODO:分析kfifo。