我们知道工作队列有三种,分别是PerCpu, Unbound,以及ORDERED这三种类型,正如之前的文档分析:
1.PerCpu的工作队列:
API:

create_workqueue(name)

这种工作队列在queue_work的时候,首先检查当前的Cpu是哪一个,然后将work调度到该cpu下面的normal级别的线程池中运行。
注:针对PerCpu类型而言,系统在开机的时候会注册2个线程池,一个低优先级的,一个高优先级的。
2.Unbound的工作队列:
API:

create_freezable_workqueue(name)

这种工作队列在queue_work的时候,同样首先检查当前的Cpu是哪一个,随后需要计算当前的Cpu属于哪一个Node,因为对于Unbound的工作队列而言,线程池并不是绑定到cpu的而是绑定到Node的,随后找到该Node对应的线程池中运行。需要留意的是这种工作队列是考虑了功耗的,例如:当work调度的时候,调度器会尽量的让已经休眠的cpu保持休眠,而将当前的work调度到其他active的cpu上去执行。

注:对于NUMA没有使能的情况下,所有节点的线程池都会指向dfl的线程池。
3.Ordered的工作队列:
API:

create_singlethread_workqueue(name) 或者 alloc_ordered_workqueue(fmt,
flags, args…)

这种work也是Unbound中的一种,但是这种工作队列即便是在NUMA使能的情况下,所有Node的线程池都会被指向dfl的线程池,换句话说Ordered的工作队列只有一个线程池,因为只有这样才能保证Ordered的工作队列是顺序执行的,而这也是本文分析的切入点。


有关并发问题的总结性陈述:

   首先对于Ordered的工作队列(create_singlethread_workqueue,其他自定义的API则不一定了)这是严格顺序执行的,绝对不可能出现并发(无论提交给wq的是否是同一个work)。


   但是对于PerCpu的工作队列(create_workqueue),其中对于提交给wq的如果是同一个work,那么也不会并发,会顺序执行。但是如果提交给wq的不是同一个work,则会在不同的cpu间并发。需要特别留意的是,其并不会在同一个CPU的不同线程间并发,这是因为create_workqueue这个API定义的max_active为1,也就意味者,当前wq只能最多在每个cpu上并发1个线程。


#define alloc_ordered_workqueue(fmt, flags, args...)         \alloc_workqueue(fmt, WQ_UNBOUND | __WQ_ORDERED | (flags), 1, ##args)
#define create_singlethread_workqueue(name)             \alloc_ordered_workqueue("%s", WQ_MEM_RECLAIM, name)

    这里面特别要去留意的是alloc_workqueue的第二个参数是flags,第三个参数表示当前工作队列max_active的work个数,比如当前值为1,那么在当前工作队列中如果已经有work在执行中了,随后排队的work只能进入pwq->delayed_works的延迟队列中,等到当前的work执行完毕后再顺序执行。

    那么这儿有个疑问就是如果将active增大,是否意味着队列中的work可以并行执行了呢,也不全是,如果当前排队的work和正在执行的work是同一个的话则需要等待当前work执行完成后顺序执行。如果当前排队的work和正在执行的work不是同一个同时alloc_workqueue函数的第三个参数(max_active)大于1的话,那么内核会为你在线程池中开启一个新的线程来执行这个work。

OK,Read The Fuck Source.

kernel\Workqueue.c

static void __queue_work(int cpu, struct workqueue_struct *wq,struct work_struct *work)
{...../*1. 当第一次调度的时候,由于pwq->nr_active为0,低于max_active(1),则将work加入到线程池中的worklist中,pwq->nr_active自增.2. 当第二次调度的时候,且第一次调度的work正在执行中(进入function了),由于pwq->nr_active为1,不低于max_active(1),则将work加入到线程池的delayed_works延迟列表中,并设置当前work的flag为WORK_STRUCT_DELAYED.*/if (likely(pwq->nr_active < pwq->max_active)) {trace_workqueue_activate_work(work);pwq->nr_active++;worklist = &pwq->pool->worklist;} else {work_flags |= WORK_STRUCT_DELAYED;worklist = &pwq->delayed_works;}//如上面的描述插入到对应的链表中    insert_work(pwq, work, worklist, work_flags);....
}

static void insert_work(struct pool_workqueue *pwq, struct work_struct *work,struct list_head *head, unsigned int extra_flags)
{struct worker_pool *pool = pwq->pool;  //设置work的flagset_work_pwq(work, pwq, extra_flags);//将work加入到对应的线程池的worklist或者delayed_works链表中list_add_tail(&work->entry, head);...//从线程池中取出处于idle的线程,唤醒它if (__need_more_worker(pool))wake_up_worker(pool);
}

我们继续看看唤醒的线程中是怎么处理的,是使用这个唤醒的idle线程呢?还是在原有的线程处理结束后再执行?

static int worker_thread(void *__worker)
{...
woke_up:/*如下所示1.针对第一次调度的情况,pool的worklist不为NULL,且pool->nr_running为0(意味着所有的worker都进入了阻塞状态),则当前唤醒的线程将继续处理这个work。2.针对第二次调度的情况,且第一次调度的work正在执行中(进入function了),那么由于pool的worklist为NULL(该work进入了延迟队列),那么,当前唤醒的worker会直接睡眠。*/if (!need_more_worker(pool))goto sleep;if (unlikely(!may_start_working(pool)) && manage_workers(worker))goto recheck;...do {...process_one_work(worker, work);...} while (keep_working(pool));....
sleep:worker_enter_idle(worker);__set_current_state(TASK_INTERRUPTIBLE);spin_unlock_irq(&pool->lock);schedule();goto woke_up;
}

我们再看看process_one_work的执行过程

static void process_one_work(struct worker *worker, struct work_struct *work)
{...//这里的理解也是非常的重要的/*首先在线程池中正在运行的线程中取出正在运行的work和当前想要处理的work进行比对,如果是同一个work那么直接返回,等待原先的那个work处理结束后再紧接着处理。*/collision = find_worker_executing_work(pool, work);if (unlikely(collision)) {move_linked_works(work, &collision->scheduled, NULL);return;}...//真正处理这个work的地方worker->current_func(work);...//判断是否要处理延迟队列的workpwq_dec_nr_in_flight(pwq, work_color);...
}

看看延迟队列是怎么提取出来的

static void pwq_dec_nr_in_flight(struct pool_workqueue *pwq, int color)
{    ...//当目前的work处理完成后,就可以将当前工作队列(ordered类型)active的work减1到0了,也就是说当前工作队列(ordered类型)又可以接收新的work了pwq->nr_active--;//如果之前的延迟队列有待处理的work,那么取出来加到pool->worklist,等到线程的下一次while循环的时候执行。/*流程如下:pwq_activate_first_delayed->pwq_activate_delayed_work->move_linked_works*/if (!list_empty(&pwq->delayed_works)) {/* one down, submit a delayed one */if (pwq->nr_active < pwq->max_active)pwq_activate_first_delayed(pwq);}...
}static void pwq_activate_delayed_work(struct work_struct *work)
{struct pool_workqueue *pwq = get_work_pwq(work);trace_workqueue_activate_work(work);move_linked_works(work, &pwq->pool->worklist, NULL);__clear_bit(WORK_STRUCT_DELAYED_BIT, work_data_bits(work));pwq->nr_active++;
}

最后说明一下2个问题:

  1. pool->nr_running,这个flag表示当前线程池是否是阻塞或者Active状态。
    0: 阻塞状态,work的function中有可能调用了导致sleep的函数,例如msleep,wait_interrupt,mutex等。这种情况下如果再次insert_work的话,需要在当前线程池中,开启新的线程(这个线程有可能是在当前CPU的不同线程或者是不同的CPU上)去处理。
    1:Active状态,work的function还在执行中,且没有导致sleep的操作。这种情况下如果再次insert_work的话,不需要再开启新的线程了,直接在原有线程中处理即可。
  2. 第二个问题是针对unbound的工作队列,其线程池是否需要额外创建的原则是属性是否一致,属性匹配只关注2个地方,一个是优先级,一个是cpumask(当前工作是否可以在对应的cpu上运行)。

如何理解create_singlethread_workqueue是严格按照顺序执行的相关推荐

  1. 按照顺序执行_问一个多线程的问题:如何才能保证线程有序执行?

    面试的时候你是否经常被问到这样的问题: 你一般通过什么方式去控制线程的执行顺序? 碰到这样的问题,我的内心其实是很抵触的! 开什么玩笑?我怎么会控制它呢?我为什么要控制它? 其实不用慌,这个问题并不难 ...

  2. 未定义函数或变量_变量提升:JavaScript是顺序执行,为什么变量在定义之前执行不会报错而是报Underfined...

    showName() console.log(myname) var myname = 'hhh' function showName() {console.log('函数showName被执行'); ...

  3. c语言中多线程的执行顺序,ReentrantLock实现 多线程顺序执行任务

    题目摘自:偏头痛杨 最近看了这位博主的文章 写的挺好的 跟着里面的线程 温习了一遍 结尾处有道题算是复习巩固吧 我是用ReentrantLock实现的 而不是synchronized 题目:使用3个线 ...

  4. js设置ajax执行顺序,ajax同步处理(使得JS按顺序执行)

    在项目中碰到一个问题: 图一: 图二: 函数1代码:这里是因为有ajax请求,默认的是异步的 //点击分页页码,请求后台返回对应页码的数据 function getdata(fewPage,flag, ...

  5. 线程安全(二)Lock 什么是Lock线程锁?与synchronized区别在哪?Lock锁是如何实现等待通知的?如何实现线程顺序执行?

    文章目录 前言 一.synchronized的缺陷 二.Lock接口的特性及基本方法 接口的特性 接口基本的方法: 三.ReentrantLock介绍及实例 ReentrantLock类常见方法: l ...

  6. Java中如何让线程按照自己指定的顺序执行?

    我们在日常的多线程开发中,可能有时会想让每个线程都按照我们指定的顺序来运行,而不是让CPU随机调度,这样可能会让我们在日常的开发工作中带来不必要的麻烦.既然有了这个需求,也就引入了本文的标题,让线程按 ...

  7. python如何控制程序的运行顺序_Python流程控制-1 顺序执行

    流程控制指的是代码运行逻辑.分支走向.循环控制,是真正体现我们程序执行顺序的操作.流程控制一般分为顺序执行.条件判断和循环控制. 顺序执行 Python代码在执行过程中,遵循下面的基本原则: 普通语句 ...

  8. android 顺序执行任务

    在项目中遇到这样的一种情况 想要让某些任务顺序执行 但是如果用多线程的话是无法保证顺序的 所以这里我就采用单线程的方式去处理 第一种方式 Rxjava 结合 Executors去先单线程执行 Exec ...

  9. jmeter 线程执行顺序_面试官让我说出8种线程顺序执行的方法!我懵了

    https://www.cnblogs.com/wenjunwei/p/10573289.html 一.前言 本文使用了8种方法实现在多线程中让线程按顺序运行的方法,涉及到多线程中许多常用的方法,不止 ...

  10. 多线程顺序消费MySQL数据_关于MQ的几件小事(五)如何保证消息按顺序执行

    1.为什么要保证顺序 消息队列中的若干消息如果是对同一个数据进行操作,这些操作具有前后的关系,必须要按前后的顺序执行,否则就会造成数据异常.举例: 比如通过mysql binlog进行两个数据库的数据 ...

最新文章

  1. java中如何应对读改写场景
  2. 电商购物网站开发需要注意这些问题
  3. Android【FileInputStream、FileOutputStream---本地文件I/O操作-读写操作模板(btnWrite方法、btnRead方法)】
  4. php make,安装PHP出现make:
  5. c语言图像函数怎么用,请教 怎么才能用C输出一个函数的图像?大侠 帮帮忙啊...
  6. 软考系统架构师笔记-最后知识点总结(五)
  7. 【NIPS2018】Spotlight及Oral论文汇总
  8. 海龟交易策略要点总结
  9. 从Helm仓库创建应用流程示例
  10. BZOJ 4043 [HAOI2015]树上操作 dfs序 线段树
  11. 关键词词云怎么做_网友问:做独立站,怎么做关键词排名,怎么做客户流量?...
  12. 【Win 10应用开发】分阶段进行数据绑定
  13. JSON格式输出Struts2
  14. 南方cass快捷键命令修改在哪_【干货】南方cass快捷键命令大全
  15. IDEA导入已有Maven项目
  16. csf格式转换--逼自己一把
  17. python 一个简单的网站采集
  18. 内存卡删除的视频能恢复吗?四个步骤
  19. 学习自旋电子学的笔记06:“扫参数”批量微磁模拟,ubermag介绍,微磁模拟求助
  20. 【考】现代传感器技术作业一至四 复习用

热门文章

  1. JavaScript要点 (一) 变量-作用域
  2. 区分.net、c#、asp.net三者间的关系
  3. (转载)JavaScript中的原型和对象机制
  4. nginx upstream配置_nginx + ingress + gunicorn 环境上传大文件报错问题的解决思路
  5. Netty4.0学习笔记系列之四:混合使用coder和handler
  6. ListenalbeFuture的使用总结
  7. 使用Ionic3创建原生app系统入门
  8. 报错 ERROR in static/js/vendor.b3f56e9e0cd56988d890.js from UglifyJs
  9. Repost: An introduction to Linux IPC by Michael Kerrisk -- IPC 分类
  10. Redis命令拾遗四——集合类型(命令补充)