UtilBox(ub)基础组件 -- 并发任务队列Taskqueue/TaskDispatcher (一)

        Task dispatcher, 任务分发队列,也可以叫做Taskqueue。之前好多同学看到这篇文章内容是“.....”,我是想做一个标记,想写这篇文章,但是没填内容,看的同学还挺多的,以为我是标题党,这里首先表示一下歉意。下次不会了哈。


        1. Taskqueue模型简介

        2. Taskqueue的用处和优势

        3. Taskqueue适用场景

        4. 实现的技术点


        1. 模型简介

言归正传,任务队列,顾名思义,就是一个可以放任务并执行的队列。试想这么一个场景,技术经理给他下面的开发人员一些开发任务,这些任务之间没有联系,都是相互独立的小模块,经理把这些小任务按顺序放到写在卡片上并放粘贴在小黑板上,这些开发人员一个一个的从黑板上领取自己的任务,并摘下卡片,然后去闷头开发,最后做完了把结果放回去。在这个场景里,经理只管往小黑板上放任务就可以了,没任务可发放了就去喝咖啡歇着。开发人员只管从小黑板上领任务去开发就行了,不用管经理,没任务做了,就可以"度假"去了(虽然不会真的放假哈)。

        可以看出,经理和开发人员只要关注小黑板这个公共地方就可以了,实际上并不需要知道对方的存在,只要做好自己的任务。这就是典型的 master+worker thread 队列分发模式。就是咱们今天所说的Taskqueue(Task Dispatcher),之后不对这两个概念做本质区分。先来看一个图:


         Taskqueue整体由三部分组成,Master线程线程安全的Queue多个worker进程,worker进程的个数可以根据cpu个数进行配置。比如8核cpu我们可以设置worker为6。这样可以并行的取任务然后执行。衔接两个模块的queue需要时线程安全的,才能保证任务的唯一性和worker的并发。

        归纳一下大体流程:

        1、Master线程接到任务(比如网卡IO读写任务,磁盘读取任务,耗CPU计算的任务),把任务放到queue中。

        2、某个等待任务的worker线程会被通知接受任务并从队列里取出。

        3、在线程中执行任务。

        4、调用Task->Callback()函数,此函数是Master设置的任务完成回调函数,比如IO读取后打印出来。

        

        2. 用处和优势

        通过上图可以看出,首先,Taskqueue是个多线程的模型,多个worker可以跑在多个cpu core上,因为worker之间不存在交互和锁,这就发挥了多核优势可以并行执行。

        第二,在多线程下,可以增加任务的吞吐量。比如我们有个N个线程,如果没有queue的话,现在有N个任务到来,没有worker可以做了,那么结果就是要么拒绝执行,要么等待某个线程空闲出来。Taskqueue通过queue来充当buffer的作用,将大量的任务缓冲在这里等待执行。master的作用就是接受任务并存下来,所以系统的吞吐量取决于master的接收速度(注意是只接受)和queue的最大长度。当量任务可以被接下来(哪怕之后慢慢做)

          第三,可以实现“异步化”。master线程从前面的逻辑接收到任务后,放入queue中就可以返回给调用者了,并不会阻塞在任务的执行阶段。而是只告诉调用者,任务接下来了,什么时候做完了就会回调之前设置的Callback。所以不会阻塞。实现了逻辑上的异步化,让调用者可以做其他逻辑。


        3. 使用场景

        估计大家对他的适用的地方也有了了解,它适用于这种任务消耗时间稍大,并且不需要同步的取到结果的逻辑。只需要扔到给master,然后就可以干其他事情了。举个例子,我们下载批量下载一些种子文件,打开迅雷,种子会被分解成一个个下载小文件的任务,然后丢给迅雷去做下载,这时候你就可以继续上网干别的了。

        再举一个例子,比如我们的c++代码调用新浪微博的接口来发送微博。我们有个publishWeibo(std::string content) ,每个content是一条微博。现在我们要发送10条微博,我们可能要for循环10次然后调用函数。这时候进程会阻塞在这里,等网络的应答。我们可以用任务队列的方式,直接把publishWeibo封装成一个task,然后扔给Master线程,Master线程就会立即返回不用等待发微博网络的应答,丢给后台的worker线程慢慢做。


        4. 实现的技术点

        1. 首先我们的master线程通过pthread_create要初始化一些worker线程,然后线程执行的回调函数就是一个死循环执行任务。

for(int i=0;i!=worker_thread_num;i++) {
    pthread_t pid;
    pthread_create(&pid,NULL,loop,this);
}

void* loop(void* context) {
     ....
     
     while(!stop) {
          task = queue.pop();
          task->execute();
          task->callback();
     }

     .....
}

        2. master要提供一个post函数接口,用来接收发过来的任务,并存入queue中,并通知worker来取

void Taskqueue::post(task) {
       queue.push(task);
        // 通过linux的“条件变量”方式,通知拥有这个条件变量的某个线程,   
        pthread_cond_signal(this->notifier);

}

        Baidu云的bae和新浪的sae都提供了这样类似的接口,也可以看一下他们的文档适用一下。接下来的文章会讲一下任务队列的具体实现。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Resource Page Description 在以前的文章中,我曾多次强调应用程序中异步化的重要性。尤其对于IO密集型操作,异步执行对于应用程序的响应能力和伸缩性有非常关键的影响。正确使用异步编程能够使用尽可能少的线程来执行大量的IO密集型操作。可惜的是,即时异步编程有避免线程阻塞等诸多好处,但是这种编程方式至今没有被大量采用。其原因有很多,其中最主要的一点可能就是异步模型在编程较为困难,导致许多开发人员不愿意去做。 异步,则意味着一个任务至少要被拆分为“二段式”的调用方式:一个方法用于发起异步请求,另一个方法用于异步任务完成后的回调。与传统方法调用方式相比,异步调用时的中间数据不能存放在线程栈上,方法之间的也不能简单地通过参数传递的方式来共享数据。此外,传统方法调用中的try…catch…finally,using等关键字都无法跨越方法边界,因此异步编程在处理异常,保护资源等方面也需要花更大的精力才行。如果一不小心,轻则造成资源泄露,重则使整个应用程序崩溃。 因此,无论是微软官方还是社区中都出现了一些简化异步编程方式的组件,例如微软并行与协调运行时和Wintellect's .NET Power Threading Library中的AsyncEnumerator。同时,我基于AsyncEnumerator构建了一个AsyncTaskDispatcher组件,使多个有依赖关系的异步操作之间的协作调用得以大大简化。 以上是引用,自己做了个更简单的demo
好的,我可以回答这个问题。以下是一个简单的 Qt 5.15.2 线程池任务队列的实现: 首先,我们需要定义一个任务类,它包含一个执行任务的函数: ``` class Task { public: virtual void run() = 0; }; ``` 然后,我们定义一个任务队列类,它包含一个任务队列和一个线程池: ``` class TaskQueue { public: TaskQueue(int threadCount) { for (int i = 0; i < threadCount; ++i) { QThread* thread = new QThread(this); thread->start(); m_threads.append(thread); } } ~TaskQueue() { for (int i = 0; i < m_threads.size(); ++i) { m_threads[i]->quit(); m_threads[i]->wait(); } } void addTask(Task* task) { m_mutex.lock(); m_tasks.append(task); m_mutex.unlock(); m_semaphore.release(); } private: QList<QThread*> m_threads; QList<Task*> m_tasks; QMutex m_mutex; QSemaphore m_semaphore; }; ``` 在任务队列类中,我们使用一个信号量来控制线程池中的线程数量。当有新任务添加到任务队列中时,我们会释放一个信号量,让一个线程来执行任务。 最后,我们定义一个任务执行器类,它会从任务队列中取出任务并执行: ``` class TaskExecutor : public QObject { Q_OBJECT public: TaskExecutor(TaskQueue* taskQueue) : m_taskQueue(taskQueue) { connect(taskQueue, &TaskQueue::taskAdded, this, &TaskExecutor::onTaskAdded); } private slots: void onTaskAdded() { while (true) { m_taskQueue->m_semaphore.acquire(); m_taskQueue->m_mutex.lock(); if (m_taskQueue->m_tasks.isEmpty()) { m_taskQueue->m_mutex.unlock(); continue; } Task* task = m_taskQueue->m_tasks.takeFirst(); m_taskQueue->m_mutex.unlock(); task->run(); delete task; } } private: TaskQueue* m_taskQueue; }; ``` 在任务执行器类中,我们使用一个死循环来不断地从任务队列中取出任务并执行。 最后,我们可以使用以下代码来创建一个线程池任务队列: ``` TaskQueue taskQueue(4); TaskExecutor taskExecutor(&taskQueue); for (int i = 0; i < 10; ++i) { taskQueue.addTask(new MyTask(i)); } ``` 其中,`MyTask` 是我们自己定义的任务类,它继承自 `Task` 并实现了 `run` 函数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值