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

C++多线程磁盘扫描出现访问违规错误求助

问题:多线程磁盘扫描Trie树构建时的访问违规错误

尝试用多线程扫描系统磁盘并构建存储所有文件路径的Trie树,但测试中出现异常:在main.cpp的循环里,线程数i为2、4、6时程序运行正常,但i为8或10时(偶尔i=8能正常运行),程序会在WorkStealQueue::pop函数的if (m_tasks.empty()) return false;行触发访问违规错误,错误信息为“Access violation reading location 0x00000014”。

已尝试的排查动作:

  • 检查WorkStealQueue类的pop和push方法实现
  • 调整原子变量m_taskCount的内存序
  • 改进WorkStealThreadPool的线程同步与任务终止逻辑
    均未解决问题。

问题分析

访问违规错误0x00000014表明程序在访问无效指针(偏移0x14意味着基指针为nullptr)。定位到WorkStealQueue::pop中的m_tasks.empty(),说明WorkStealQueue对象的内存已被破坏或对象已失效,而线程仍在访问它。

核心原因在于**WorkStealThreadPool::addTask函数依赖未初始化的线程本地变量**:

  • WorkStealThreadPool::m_index是线程本地变量,仅在worker线程启动时被赋值为有效队列索引
  • 主线程调用addTask时,自身的m_index副本未被初始化,值为未定义的随机值
  • 当随机值超出m_taskQueues的有效索引范围时,会触发数组越界访问,破坏内存结构,最终导致worker线程访问队列时出现访问违规

线程数越大,越容易触发该问题:因为主线程m_index的随机值超出队列数量范围的概率随队列数量增加而变化,当队列数量为8/10时,随机值更易命中无效索引,破坏内存。


修复方案

1. 初始化线程本地变量

显式初始化线程本地变量m_index为无效值,用于区分worker线程和外部线程:
在workstealthreadpool.cpp中修改初始化代码:

#include <limits>
thread_local std::size_t WorkStealThreadPool::m_index = std::numeric_limits<std::size_t>::max();

2. 修改addTask函数,安全选择任务队列

修改addTask逻辑,判断当前线程是否为worker线程:

  • 如果是worker线程,使用自身的任务队列
  • 如果是外部线程(如主线程),通过轮询方式选择任务队列,避免越界访问

首先在WorkStealThreadPool类中添加一个原子变量用于轮询计数:
在workstealthreadpool.h的private段添加:

std::atomic<std::size_t> m_nextQueueIndex{0};

然后修改addTask模板函数:

template<typename Callback, typename... Args>
auto WorkStealThreadPool::addTask(Callback&& func, Args&&... args) -> std::future<typename std::result_of<Callback(Args...)>::type>
{
    using returnType = typename std::result_of<Callback(Args...)>::type;
    auto task = std::make_shared<std::packaged_task<returnType()>>(std::bind(std::forward<Callback>(func), std::forward<Args>(args)...));
    std::future<returnType> result = task->get_future();

    std::size_t queueIndex;
    // 判断当前线程是否为worker线程(m_index在有效范围内)
    if (m_index < m_taskQueues.size()) {
        queueIndex = m_index;
    } else {
        // 外部线程轮询选择队列,避免越界
        queueIndex = m_nextQueueIndex.fetch_add(1, std::memory_order_relaxed) % m_taskQueues.size();
    }

    m_taskQueues[queueIndex]->push([task]() { (*task)(); });

    return result;
}

3. 可选优化:修复Trie树清理逻辑

Scanner::clearTrie函数在删除子节点后未清空父节点的childs映射,虽不影响崩溃修复,但可避免悬空指针:

void Scanner::clearTrie(TrieNode* root)
{
    if (root == nullptr) return;
    for (auto iter = root->childs.begin(); iter != root->childs.end(); ++iter)
    {
        clearTrie(iter.key());
    }
    root->childs.clear();
    delete root;
}

完整代码实现

workstealthreadpool.h

#pragma once

#include <deque>
#include <functional>
#include <mutex>
#include <thread>
#include <future>
#include <vector>
#include <atomic>

class WorkStealQueue
{
public:
    WorkStealQueue() = default;
    WorkStealQueue(const WorkStealQueue& rhs) = delete;
    WorkStealQueue& operator=(const WorkStealQueue& rhs) = delete;
    ~WorkStealQueue() = default;

    void push(std::function<void()> task);
    bool pop(std::function<void()>& task);
    bool steal(std::function<void()>& task);

private:
    std::deque<std::function<void()>> m_tasks;
    std::mutex m_mutex;
};

class WorkStealThreadPool
{
public:
    explicit WorkStealThreadPool(std::size_t threadNums)
        : m_stop(false), m_nextQueueIndex(0) { init(threadNums); }
    ~WorkStealThreadPool();

    template<typename Callback, typename... Args>
    auto addTask(Callback&& func, Args&&... args)->std::future<typename std::result_of<Callback(Args...)>::type>;

private:
    void init(std::size_t threadNums);
    bool stealTask(std::function<void()>& task);
    void worker(size_t index);

private:
    std::vector<std::thread> m_workThreads;
    std::vector<std::unique_ptr<WorkStealQueue>> m_taskQueues;
    std::atomic<bool> m_stop;
    std::atomic<std::size_t> m_nextQueueIndex;
    static thread_local std::size_t m_index;
};

template<typename Callback, typename... Args>
auto WorkStealThreadPool::addTask(Callback&& func, Args&&... args) -> std::future<typename std::result_of<Callback(Args...)>::type>
{
    using returnType = typename std::result_of<Callback(Args...)>::type;
    auto task = std::make_shared<std::packaged_task<returnType()>>(std::bind(std::forward<Callback>(func), std::forward<Args>(args)...));
    std::future<returnType> result = task->get_future();

    std::size_t queueIndex;
    if (m_index < m_taskQueues.size()) {
        queueIndex = m_index;
    } else {
        queueIndex = m_nextQueueIndex.fetch_add(1, std::memory_order_relaxed) % m_taskQueues.size();
    }

    m_taskQueues[queueIndex]->push([task]() { (*task)(); });

    return result;
}

workstealthreadpool.cpp

#include "workstealthreadpool.h"
#include <limits>

void WorkStealQueue::push(std::function<void()> task)
{
    std::lock_guard<std::mutex> lock(m_mutex);
    m_tasks.emplace_back(std::move(task));
}

bool WorkStealQueue::pop(std::function<void()>& task)
{
    std::lock_guard<std::mutex> lock(m_mutex);
    if (m_tasks.empty()) return false;
    task = std::move(m_tasks.front());
    m_tasks.pop_front();
    return true;
}

bool WorkStealQueue::steal(std::function<void()>& task)
{
    std::lock_guard<std::mutex> lock(m_mutex);
    if (m_tasks.empty()) return false;
    task = std::move(m_tasks.back());
    m_tasks.pop_back();
    return true;
}

thread_local std::size_t WorkStealThreadPool::m_index = std::numeric_limits<std::size_t>::max();

void WorkStealThreadPool::init(std::size_t threadNums)
{
    for (std::size_t i = 0; i < threadNums; ++i)
    {
        m_taskQueues.emplace_back(std::make_unique<WorkStealQueue>());
        m_workThreads.emplace_back(&WorkStealThreadPool::worker, this, i);
    }
}

WorkStealThreadPool::~WorkStealThreadPool()
{
    m_stop = true;
    for (std::thread& workerThread : m_workThreads)
    {
        if (workerThread.joinable())
        {
            workerThread.join();
        }
    }
}

bool WorkStealThreadPool::stealTask(std::function<void()>& task)
{
    for (std::size_t i = 0; i < m_taskQueues.size(); ++i)
    {
        std::size_t index = (m_index + i + 1) % m_taskQueues.size();
        if (m_taskQueues[index]->steal(task))
        {
            return true;
        }
    }
    return false;
}

void WorkStealThreadPool::worker(std::size_t index)
{
    m_index = index;
    while (!m_stop)
    {
        std::function<void()> task;
        if (m_taskQueues[m_index]->pop(task) || stealTask(task))
        {
            task();
        }
        else
        {
            std::this_thread::yield();
        }
    }
}

scanner.h

#pragma once

#include "workstealthreadpool.h"
#include <QMap>
#include <QString>

struct TrieNode
{
    QMap<TrieNode*, QString> childs;
    TrieNode* parent = nullptr;
};

class Scanner
{
public:
    explicit Scanner(std::size_t threadNums);
    virtual ~Scanner();
    virtual void scanDrives(const QStringList& drives);
    virtual bool isScanCompleted();
    virtual std::vector<TrieNode*> fetchScanResults();

private:
    void scanCore(const QString& currentPath, TrieNode* parent);
    void clearTrie(TrieNode* root);

private:
    TrieNode* m_root;
    WorkStealThreadPool* m_threadPool;
    std::mutex m_mutex;
    std::vector<TrieNode*> m_fileNodes;
    std::atomic<int> m_taskCount;
};

#ifdef _DEBUG
void Print(TrieNode* root);
void Print(std::vector<TrieNode*>* fileNodes);
#endif

scanner.cpp

#include "scanner.h"
#include <QDir>

Scanner::Scanner(std::size_t threadNums)
    : m_root(nullptr)
    , m_threadPool(nullptr)
    , m_taskCount(0)
{
    if (threadNums != 0)
    {
        m_threadPool = new WorkStealThreadPool(threadNums);
    }
}

Scanner::~Scanner()
{
    delete m_threadPool;
    clearTrie(m_root);
}

void Scanner::scanDrives(const QStringList& drives)
{
    clearTrie(m_root);
    m_fileNodes.clear();
    m_root = new TrieNode();
    for (const QString& drive : drives)
    {
        TrieNode* child = new TrieNode();
        child->parent = m_root;
        m_root->childs[child] = drive;
        scanCore(drive, child);
    }
}

bool Scanner::isScanCompleted()
{
    return m_taskCount.load(std::memory_order_acquire) == 0;
}

std::vector<TrieNode*> Scanner::fetchScanResults()
{
    if (!isScanCompleted())
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        return m_fileNodes;
    }
    return m_fileNodes;
}

void Scanner::scanCore(const QString& currentPath, TrieNode* parent)
{
    QDir dir(currentPath);
    if (!dir.exists()) return;

    QStringList fileNames = dir.entryList(QDir::Files);
    for (const QString& fileName : fileNames)
    {
        TrieNode* child = new TrieNode();
        child->parent = parent;
        parent->childs[child] = fileName;

        std::lock_guard<std::mutex> lock(m_mutex);
        m_fileNodes.emplace_back(child);
    }

    QStringList subdirNames = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
    for (const QString& subdirName : subdirNames)
    {
        QString childPath = currentPath + QDir::separator() + subdirName;
        TrieNode* child = new TrieNode();
        child->parent = parent;
        parent->childs[child] = subdirName + "/";

        if (m_threadPool)
        {
            m_taskCount.fetch_add(1, std::memory_order_release);
            m_threadPool->addTask([this, childPath, child]
                {
                    scanCore(childPath, child);
                    m_taskCount.fetch_sub(1, std::memory_order_acquire);
                }
            );
        }
        else {
            scanCore(childPath, child);
        }
    }

}

void Scanner::clearTrie(TrieNode* root)
{
    if (root == nullptr) return;
    for (auto iter = root->childs.begin(); iter != root->childs.end(); ++iter)
    {
        clearTrie(iter.key());
    }
    root->childs.clear();
    delete root;
}

#ifdef _DEBUG
void Print(TrieNode* root)
{
    static int level = 0;
    if (root == nullptr || root->childs.empty()) return;
    for (auto iter = root->childs.begin(); iter != root->childs.end(); ++iter)
    {
        qDebug().noquote() << QString("    ").repeated(level) << iter.value();
        ++level;
        Print(iter.key());
        --level;
    }
}
#endif

#ifdef _DEBUG
void Print(std::vector<TrieNode*>* fileNodes)
{
    for (TrieNode* fileNode : *fileNodes)
    {
        qDebug().noquote() << fileNode->parent->childs[fileNode];
    }
}
#endif

main.cpp

#include "scanner.h"
#include <QStorageInfo>
#include <chrono>

void TestScanDrives(Scanner& scanner, const QStringList& drives)
{
    scanner.scanDrives(drives);
    while (!scanner.isScanCompleted())
    {
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

int main()
{
    QStringList drives;
    for (const QStorageInfo& drive : QStorageInfo::mountedVolumes())
    {
        if (drive.isValid() && drive.isReady())
        {
            drives << drive.rootPath();
        }
    }

    for (int i = 2; i <= 10; i += 2)
    {
        Scanner scanner(i);
        TestScanDrives(scanner, drives);
    }
    
    return 0;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 05:42:03