Skip to content

线程池的实现中存在错误 #24

@Mq-b

Description

@Mq-b

start 成员函数:

void start(){
    for (std::size_t i = 0; i < num_threads_; ++i){
        pool_.emplace_back([this] {
            while (!stop_) {
                Task task;
                {
                    std::unique_lock<std::mutex> lc{ mutex_ };
                    if (tasks_.empty())
                        return;
                    cv_.wait(lc, [this] {return stop_ || !tasks_.empty(); });
                    task = std::move(tasks_.front());
                    tasks_.pop();
                }
                task();
            }
        });
    }
}

问题在于:

std::unique_lock<std::mutex> lc{ mutex_ };
if (tasks_.empty())
    return;

这样写可能导致线程开始执行,任务还没加入,队列为空,就直接退出了

ThreadPool pool{ 4 }; // 构造函数调用 start 成员函数,任务还未加入,直接退出
// ...
pool.submit(task); // 任务还未加入

有时你可以看到任务正确执行,有时不行。这个问题的原因很简单: pool_.emplace_back 只是构造线程对象,线程对象构造代表启动线程,但是线程何时执行,是系统调度的事情,也和硬件有所关系。

正常的做法我们应该是将判断移动到 cv_.wait 的下面,也就是:

std::unique_lock<std::mutex> lc{ mutex_ };
cv_.wait(lc, [this] {return stop_ || !tasks_.empty(); });
if (tasks_.empty())
    return;

join() 成员函数:

void join(){
    for (auto& thread : pool_) {
        if (thread.joinable()) {
            thread.join();
        }
    }
    pool_.clear();
}

但是一旦改成这样,我们的 join() 成员函数则可能出现阻塞,这是因为:

  1. 假设线程池有三个线程,添加了 2 个任务给线程池执行,线程 1 2 去执行完毕了,任务队列也就空了

  2. 然而线程 3 在调用 wait 函数的时候被阻塞了。

    cv_.wait(lc, [this] {return stop_ || !tasks_.empty(); });
  3. 我们调用 join() 成员函数,另外两个线程正常的退出,但是线程 3 会被一直阻塞在 wait 无法退出,因为条件始终无法满足,stop_ 只有在调用 stop() 成员函数后才会为 true,而 !tasks_.empty() 因为线程队列已经为空,所以始终为 false将一直阻塞

修改

放弃 join() 成员函数并修改给出的使用示例,

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingissue-resolved问题已解决

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions