You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux与Windows环境下线程池代码运行差异及段错误排查

问题:线程池代码在Linux下触发段错误,Windows运行正常的原因分析

现象描述

Windows环境运行结果:

Start A的地址:xxxxxxxxxxxxxx
Deal A的地址:xxxxxxxxxxxxxx
Read A的地址:xxxxxxxxxxxxxx

Linux环境运行结果:

Start A的地址:xxxxxxxxxxxxxx
Deal A的地址:xxxxxxxxxxxxxx
segmentation fault

相关代码

template<typename T>
class SafeQueue {
public:
    bool empty() {
        shared_lock<shared_mutex>lc(_m);
        return _que.empty();
    }
    auto size() {
        shared_lock<shared_mutex>lc(_m);
        return _que.size();
    }
    void push(T& t) {
        unique_lock<shared_mutex> lc(_m);
        _que.push(t);
    }
    bool pop(T& t) {
        unique_lock<shared_mutex> lc(_m);
        if (_que.empty())return false;
        t = move(_que.front());
        _que.pop();
        return true;
    }
private:
    queue<T>_que;
    shared_mutex _m;
};
class ThreadPool {
private:
    class worker {
    public:
        worker(ThreadPool* _pool) : pool{ _pool } {}
        void operator ()() {
            while (!pool->_isShutDown) {
                {
                    unique_lock<mutex> lock(pool->_m);
                    pool->_cv.wait(lock, [this]() {
                        return this->pool->_isShutDown ||
                            !this->pool->_que.empty();
                        });
                }
                function<void()>func;
                bool flag = pool->_que.pop(func);
                if (flag) {
                    func();
                }
            }
        }
    private:
        ThreadPool* pool;
    };
public:
    ThreadPool(int n) : _threads(n), _isShutDown{ false } {
        for (auto& t : _threads)t = thread{ worker(this) };
    }
    ThreadPool(const ThreadPool&) = delete;
    ThreadPool(ThreadPool&&) = delete;
    ThreadPool& operator=(const ThreadPool&) = delete;
    ThreadPool& operator=(ThreadPool&&) = delete;

    template <typename F, typename... Args>
    auto submit(F&& f, Args &&...args) -> std::future<decltype(f(args...))> {
        function<decltype(f(args...))()> func = [&f, args...]() {return f(args...); };
        auto task_ptr = std::make_shared<std::packaged_task<decltype(f(args...))()>>(func);
        std::function<void()> warpper_func = [task_ptr]() {
            (*task_ptr)();
        };
        _que.push(warpper_func);
        _cv.notify_one();
        return task_ptr->get_future();
    }
    ~ThreadPool() {
        auto f = submit([]() {});
        f.get();
        _isShutDown = true;
        _cv.notify_all();
        for (auto& t : _threads) {
            if (t.joinable()) t.join();
        }
    }

private:
    bool _isShutDown;
    SafeQueue<std::function<void()>> _que;
    vector<std::thread>_threads;
    mutex _m;
    condition_variable _cv;
};

class A {
public:
    int a = 0;
    void P() {
        cout << 111 << endl;
    }
};

class T {
public:
    T(int num) : mThreadPool(new ThreadPool(8)) {

    };

    void Start() {
        A a;
        cout << "Start A的地址:" << &a << endl;
        Deal(a);

    }

    void Deal(A& a) {
        cout << "Deal A的地址:" << &a << endl;
        mThreadPool->submit(std::bind(&T::Read, this, std::ref(a)));
    }

    void Read(A& a) {
        cout << "Read A的地址:" << &a << endl;
    }

private:
    std::unique_ptr<ThreadPool> mThreadPool;
};

int main() {

    T t(8);
    t.Start();

    return 0;
}

原因分析

1. 栈对象生命周期过期导致悬垂引用

在T::Start()函数中,A a是栈上创建的局部对象。随后通过std::ref(a)将其引用绑定到std::bind生成的函数对象中,提交给线程池等待执行。但Start()函数执行完毕后,栈对象a会被立即销毁,内存被回收或标记为无效。

  • Windows的内存管理机制不会立即覆盖已销毁栈对象的内存区域,工作线程执行Read函数时可能还能读取到残留的内存内容,因此不会触发错误。
  • Linux的内存系统会更及时地回收栈内存,工作线程访问已失效的引用时,直接触发段错误。

2. submit函数中lambda捕获的悬垂引用问题

在ThreadPool::submit函数中,lambda表达式[&f, args...]()捕获了参数f的引用:

function<decltype(f(args...))()> func = [&f, args...]() {return f(args...); };

f是submit的函数参数,当submit函数执行完成后,f的生命周期结束,此时func中保存的引用就变成了悬垂引用。工作线程执行func()时,会访问已销毁的f对象内存,这也是触发段错误的潜在原因,不同系统的内存回收策略差异导致表现不一致。

3. 线程池析构与任务执行的竞态条件

程序结束时,main函数中的T t对象销毁,会触发ThreadPool的析构函数。析构函数中先提交空任务并等待完成,再设置_isShutDown并通知所有线程。但Start()提交的任务可能还未执行,工作线程可能在_isShutDown设置后才尝试执行任务,此时任务依赖的对象(如A a)已经销毁,进一步加剧了内存访问错误的概率。

修复建议

  • 将栈对象A a改为动态分配(如std::shared_ptr<A>),延长其生命周期至任务执行完毕。
  • 修改submit函数中的lambda捕获方式,将&f改为按值捕获f,避免悬垂引用:[f, args...]()。
  • 确保线程池中的任务执行完成后再销毁相关依赖对象,比如在T的析构函数中等待所有提交的任务完成。

内容的提问来源于stack exchange,提问作者Sunjal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 09:48:05