c++ - 安全地迭代 std::vector 而项目可能被删除

标签 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" 事件被触发时,无论是从更新循环内部还是从另一个线程,代码都会崩溃。我猜这是因为迭代器在删除元素后不再有效。在不知道删除何时发生的情况下,如何安全地迭代不断变化的 vector ?

错误表明 i 不能取消引用。我尝试用 try catch block 包装循环体,但没有捕获异常,只是来自 Visual Studio 的调试断言。

最佳答案

Without knowing when the erase happens, how can I safely iterate over the changing vector?

你不能。

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

您需要确保修改 vector 的线程阻止任何其他线程并发访问它,例如通过使用互斥体。

在 C++14 中,您可以使用 std::shared_timed_mutex 来保护 vector ,以便只需要从 vector 中读取的线程可以获取共享锁,但需要的线程修改 vector 可以在进行更改时获取独占锁。

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())
                // ...
        }
    }
};

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

这仅在更改由另一个线程完成时才有用,如果 keydown 事件由更新循环触发,它将死锁。

另一种解决方案是推迟从 vector 中删除项目并定期清理“死”项目。您仍然需要一个互斥锁来防止在修改 vector 时并发访问,但可以通过仅对一小部分代码持有锁来避免死锁。像这样的东西:

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();
    }
};

关于c++ - 安全地迭代 std::vector 而项目可能被删除,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25890526/

相关文章:

c++ - Segmentation Fault,归并排序算法

c++ - VS2010中全局变量的定义

c++ - iPhone/iPad wifi tcpip 连接到 wifi 上的其他计算机

C - 访问整数变量时的安全性 : 1 writer, N 读者

c++ - 重载 [] 和 , c++11

multithreading - OpenMP while 循环

android - 启动外部 Activity 以扫描条形码

java - 线程没有被终止;陷入循环

c# - 网络请求数量有限

c++ - boost ,以分钟为单位获得UTC时间