安全地迭代std :: vector,同时可以删除项目

时间:2014-09-17 12:25:56

标签: c++ multithreading c++11 vector iterator

我的应用程序中有一个全局事件管理器。每个组件都可以监听事件并触发它们。我们来看看窗口组件。它拥有std::vector个窗口,它会定期迭代以处理输入等等。此外,它会注册到"keydown"事件,以便在按下转义键时删除活动窗口。

class window : public module {
    window() {
        // Listen to key event
        listen("keydown", [=](size_t index, int code) {
            if (code == 27) {
                windows[index].close();
                windows.erase(windows.begin() + index);
            }
        });
    }

    void update() {
        for (auto i = windows.begin(); i != windows.end(); i++) {
            if (i->is_open()) // On remove, crashes at this line.
                // ...
        }
    }
};

问题在于,当从更新循环内部或另一个线程触发"keydown"事件时,代码崩溃。我想这是因为在擦除元素后迭代器不再有效。在不知道何时发生擦除的情况下,如何安全地迭代变化的向量?

错误表示i无法解除引用。我尝试通过try catch块包装循环体,但是没有异常可以捕获,只是来自Visual Studio的调试断言。

1 个答案:

答案 0 :(得分:8)

  

如果不知道擦除何时发生,我怎样才能安全地迭代变化的矢量?

你不能。

在被另一个线程修改时,访问std::vector(或任何其他标准容器)是未定义的行为。

您需要确保修改向量的线程阻止任何其他线程同时访问它,例如使用互斥锁。

在C ++ 14中,您可以使用std::shared_timed_mutex来保护向量,这样只需要从向量中读取的线程就可以获取共享锁,但是想要修改向量的线程可以采用在进行更改时进行独占锁定。

class window : public module {
    std::shared_timed_mutex m_mutex;

    window() {
        // Listen to key event
        listen("keydown", [=](size_t index, int code) {
            if (code == 27) {
                std::unique_lock<std::shared_timed_mutex> lock(m_mutex);
                windows[index].close();
                windows.erase(windows.begin() + index);
            }
        });
    }

    void update() {
        std::shared_lock<std::shared_timed_mutex> lock(m_mutex);
        for (auto i = windows.begin(); i != windows.end(); i++) {
            if (i->is_open())
                // ...
        }
    }
};

(N.B。我将i.is_open()更改为i->is_open(),因为我认为这是您在代码中真正拥有的内容。)

这只会在其他线程完成更改时有所帮助,如果更新循环触发了keydown事件,它将会死锁。

另一个解决方案是推迟从矢量中删除项目并定期清理&#34; dead&#34;项目。在修改向量时,仍然需要一个互斥锁来防止并发访问,但只能通过锁定一小段代码来避免死锁。类似的东西:

class window : public module {
    std::mutex m_mutex;
    std::vector<size_t> m_expired;

    window() {
        // Listen to key event
        listen("keydown", [=](size_t index, int code) {
            if (code == 27) {
                windows[index].close();
                std::lock_guard<std::mutex> lock(m_mutex);
                m_expired.push_back(index);
            }
        });
    }

    void update() {
        erase_expired();
        for (auto i = windows.begin(); i != windows.end(); i++) {
            if (i->is_open())
                // ...
        }
    }

    void erase_expired()
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        std::sort(m_expired.begin(), m_expired.end(), std::greater<>{});
        for (auto idx : m_expired)
            windows.erase(windows.begin() + idx);
        m_expired.clear();
    }
};