lzpong / threadpool

based on C++11 , a mini threadpool , accept variable number of parameters 基于C++11的线程池,简洁且可以带任意多的参数

Home Page:http://www.cnblogs.com/lzpong/p/6397997.html

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

子线程返回方式矛盾

williamWANG0325 opened this issue · comments

子线程工作函数如下

while (_run)         /****** 1/
{
	Task task; // 获取一个待执行的 task
	{
		// unique_lock 相比 lock_guard 的好处是:可以随时 unlock() 和 lock()
		unique_lock<mutex> lock{ _lock };
		_task_cv.wait(lock, [this]{
				return !_run || !_tasks.empty();
		}); // wait 直到有 task
		if (!_run && _tasks.empty())  /****** 2/
			return;       
		task = move(_tasks.front()); // 按先进先出从队列取一个 task
		_tasks.pop();
	}
	_idlThrNum--;
	task();//执行任务
	_idlThrNum++;
}

由上述代码设计可见,子线程结束的方式有两种(代码中 /******/处):

  1. 第一处,当_run为false,即线程池析构时立刻停止。(这种方式结果为,当线程池析构时立刻回收所有子进程,丢弃任务队列中所有未完成任务)
  2. 第二处,要求_run为false且任务队列为空。(这种方式结果为,当线程池析构时,先完成剩下的所有任务再结束,个人认为这种设计更符合直觉)

两种方式混合使会用导致:当线程池析构时,如果任务队列还有任务,那么所有子线程会被唤醒,执行一次任务后被回收。既没有立即回收线程,也没有完全清空任务队列,就感觉挺迷惑的,如果要修改,应该使得子线程返回方式统一

  1. 要么将/****** 2/处的&&_tasks.empty()去掉,这样也就符合上面所说的第一种情况
  2. 要么将/****** 1/处的while (_run)改为while(true),这样也就符合上面所说的第二种情况

其实我认为作者是想写出第二种情况的代码的。如果这样的话/****** 1/处可能是个小bug,这会导致线程池在析构时可能会出错。

下面有个小例子可以证明我的想法。这是一个非常简单的多线程累加器,当却会导致未定义行为。
注意,该代码我在我自己的mac上用clang编译是没有问题的。但在centos上用g++编译就会出现问题,输出的结果是0,原因是线程池中的4个子线程在执行while (_run)执行前,线程池就进行析构(将_run修改为false),导致子线程直接返回,抛弃任务队列中的所有的任务,导致结果为0。(这里有个有意思的现象,我在centos上使用gdb调试来运行,结果又是正确的)可以试着在服务器上运行一下这个代码,看会不会出现相同的情况,我的环境是:CentOS Linux release 7.9.2009 (Core)g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-44)

#include "ThreadPool.h"
int x  = 0;
std::mutex mu;

void fun() {
    std::unique_lock<std::mutex> m{mu};
    for(int i = 0; i < 10000; i++)
        x++;
}

int main() {
    {
        std::threadpool tp;
        for(int i = 0; i < 100; i++)
            tp.commit(fun);
    }//该作用域为了析构tp,确保线程池中所有线程已经运行完
    printf("%d\n", x);   // 结果应该为 1000000
}

运行结果:
image

其实类似上面这种情况发生的概率其还挺大,只要当线程池析构的时候,任务队列中还有任务就会出现这种情况,最后会导致这种迷惑行为。
其实我猜想,作者设计那个while(_run)的判定可能是为了让线程池析构的时候,commit函数无法再创建新的子进程,但我觉得就算创建了其实也问题不大,反正可以在后面返回,不会产生不好的结果。

综上所述,我建议把第90行改成while(true)

(不知道我分析的对不对...,或者作者这样设计有别的用意?)

谢谢, while(true) 是有必要 commit 2e0c5e8