【问题标题】:C++ Concurrency segfault on mutex互斥量上的 C++ 并发段错误
【发布时间】:2016-08-27 18:13:15
【问题描述】:

你好,

我对 C++ 很陌生,但我有 6 年的 Java 经验、2 年的 C 经验和一些并发基础知识。我正在尝试创建一个线程池来处理任务。它在下面与相关的测试主目录。

似乎错误是从

生成的
void ThreadPool::ThreadHandler::enqueueTask(void (*task)(void)) {
    std::lock_guard<std::mutex> lock(queueMutex);

正如我的调试器所说,但在进行传统的 cout 调试时,我发现有时它可以在没有段错误和删除的情况下工作

threads.emplace(handler->getSize(), handler);

来自ThreadPool::enqueueTask() 大大提高了稳定性。

总的来说,我认为这也与我对 condition_variable(称为 idler)的不当使用有关。

编译器:CLion 中的 minGW-w64

.cpp

#include <iostream>
#include "ThreadPool.h"

ThreadPool::ThreadHandler::ThreadHandler(ThreadPool *parent) : parent(parent) {
    thread = std::thread([&]{
        while (this->parent->alive){
            if (getSize()){
                std::lock_guard<std::mutex> lock(queueMutex);
                (*(queue.front()))();
                queue.pop_front();
            } else {
                std::unique_lock<std::mutex> lock(idlerMutex);
                idler.wait(lock);
            }
        }
    });
}

void ThreadPool::ThreadHandler::enqueueTask(void (*task)(void)) {
    std::lock_guard<std::mutex> lock(queueMutex);
    queue.push_back(task);
    idler.notify_all();
}

size_t ThreadPool::ThreadHandler::getSize() {
    std::lock_guard<std::mutex> lock(queueMutex);
    return queue.size();
}

void ThreadPool::enqueueTask(void (*task)(void)) {
    std::lock_guard<std::mutex> lock(threadsMutex);
    std::map<int, ThreadHandler*>::iterator iter = threads.begin();
    threads.erase(iter);
    ThreadHandler *handler = iter->second;
    handler->enqueueTask(task);
    threads.emplace(handler->getSize(), handler);
}

ThreadPool::ThreadPool(size_t size) {
    for (size_t i = 0; i < size; ++i) {
        std::lock_guard<std::mutex> lock(threadsMutex);
        ThreadHandler *handler = new ThreadHandler(this);
        threads.emplace(handler->getSize(), handler);
    }
}

ThreadPool::~ThreadPool() {
    std::lock_guard<std::mutex> lock(threadsMutex);
    auto it = threads.begin(), end = threads.end();
    for (; it != end; ++it) {
        delete it->second;
    }
}

.h

#ifndef WLIB_THREADPOOL_H
#define WLIB_THREADPOOL_H

#include <mutex>
#include <thread>
#include <list>
#include <map>
#include <condition_variable>

class ThreadPool {
private:
    class ThreadHandler {
        std::condition_variable idler;
        std::mutex idlerMutex;
        std::mutex queueMutex;
        std::thread thread;
        std::list<void (*)(void)> queue;
        ThreadPool *parent;
    public:
        ThreadHandler(ThreadPool *parent);
        void enqueueTask(void (*task)(void));
        size_t getSize();
    };
    std::multimap<int, ThreadHandler*> threads;
    std::mutex threadsMutex;
public:
    bool alive = true;
    ThreadPool(size_t size);
    ~ThreadPool();

    virtual void enqueueTask(void (*task)(void));
};


#endif //WLIB_THREADPOOL_H

主要:

#include <iostream>
#include <ThreadPool.h>

ThreadPool pool(3);

void fn() {
    std::cout << std::this_thread::get_id() << '\n';
    pool.enqueueTask(fn);
};

int main() {
    std::cout << "Hello, World!" << std::endl;
    pool.enqueueTask(fn);
    return 0;
}

【问题讨论】:

  • 你知道你上面的例子永远不会终止吗?该任务总是启动另一个任务。
  • 您确定地图threads 永远不会为空吗?为什么不使用std::function 作为“功能”?
  • @Steve 是的,这是为了测试更长的时间。
  • @JoachimPileborg theads 有时是空的,这就是我检查大小的原因,这有关系吗?对于函数,我比较熟悉C语法。
  • threads.erase(iter); iter 无效,之后您不能(安全地)使用它。尝试:iter = threads.erase(iter); iter 接收一个新的有效迭代器或在使用它调用erase(iter) 之前使用它。

标签: c++ concurrency mutex condition-variable


【解决方案1】:

您的main() 函数调用enqueueTask()

随后,您的main() 立即返回。

这会让您的流程运转起来。这涉及调用所有全局对象的析构函数。

ThreadPool 的析构函数然后继续删除所有动态范围的线程。

当线程仍在运行时。欢闹随之而来。

您需要实现所有线程有序关闭的流程。

这意味着将active 设置为false,踢掉小腿上的所有线程,然后加入所有线程,然后让自然顺其自然,最终摧毁一切。

附: -- 您需要修复检查alive 的方式。您还需要访问alive 线程安全,受互斥锁保护。问题是线程可能持有两个不同互斥锁之一的锁。这使得这个过程有些复杂。在这里进行一些重新设计。

【讨论】:

  • 确实是析构函数,非常感谢,没想到会有这么大的影响。为什么布尔值需要是线程安全的,因为它只更改一次为假,并且对数据类型的基元的读/写是瞬时的,
  • “线程安全”不仅仅意味着“来自多个线程的多次更新以线程安全的方式完成”,还意味着“从一个线程更新并从另一个线程读取的定义明确的语义” .只有在持有互斥锁时发生的更改才能保证在互斥锁释放后对其他线程可见(其他线程也持有互斥锁)。
  • 我仍然看不到这种情况的使用,该架构定义了一个任务周期,它首先检查父级的活跃度,如果它是活跃的,它会做它的事情,如果不是,它就会结束。我看不出互斥体的性能降低如何提供任何安全性,因为它会导致额外的循环......如果它在没有...的情况下工作,我不会放弃一条指令......
  • 不保证一个线程的可观察效果对另一个线程可见,除非它们是“排序的”。编译器看到“active=false;”在某处的某些功能中。大的交易:编译器看到函数中没有其他内容读取“活动”,并且没有函数调用,因此它不会生成代码来实际设置值,直到函数返回。这在 C++ 中是允许的。
猜你喜欢
  • 1970-01-01
  • 2015-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-06
  • 2022-01-14
  • 1970-01-01
  • 2015-11-17
相关资源
最近更新 更多