Нужно ли мьютекс операции чтения, в то время как другие потоки пишут потокобезопасно? - PullRequest
0 голосов
/ 04 ноября 2019

Я запутался в конкретной ситуации с многопоточностью и не смог найти четких объяснений этому сценарию. В приведенном ниже коде два пользовательских потока пишут + чтение потока безопасно, но основной поток также читает одновременно. Итак, вот мой вопрос: должен ли я также включать функцию чтения? Или абсолютно невозможно вывести приложение из строя, возможно, из-за ранее удаленных указателей в векторе, например? Я надеюсь, что вы, ребята, можете помочь мне, спасибо!

#include <thread>
#include <mutex>
#include <iostream>
#include <vector>

int g_i = 0;
std::vector<int> test;
std::mutex g_i_mutex;  // protects g_i

void safe_increment()
{
    std::lock_guard<std::mutex> lock(g_i_mutex);
    ++g_i;
    test.resize(test.size() + 1, 2);
    std::cout << std::this_thread::get_id() << ": " << g_i << '\n';

    for (std::vector<int>::const_iterator i = test.begin(); i != test.end(); ++i)
        std::cout << std::this_thread::get_id() << " thread vector: " << *i << '\n';
    // g_i_mutex is automatically released when lock
    // goes out of scope
}


void request_threadedvar()
{
    for (std::vector<int>::const_iterator i = test.begin(); i != test.end(); ++i)
        std::cout << std::this_thread::get_id() << " request threaded vector: " << *i << '\n';
}

int main()
{
    std::cout << "main: " << g_i << '\n';
    test.resize(test.size() + 1, 1);
    for (std::vector<int>::const_iterator i = test.begin(); i != test.end(); ++i)
        std::cout << "main vector: " << *i << '\n';

    std::thread t1(safe_increment);
    request_threadedvar();
    std::thread t2(safe_increment);

    t1.join();
    t2.join();

    std::cout << "main: " << g_i << '\n';
    for (std::vector<int>::const_iterator i = test.begin(); i != test.end(); ++i)
        std::cout << "main vector: " << *i << '\n';

}

1 Ответ

0 голосов
/ 04 ноября 2019

Оба std::thread потока выполнения, а также исходный main поток выполнения вызывают различные методы одного и того же объекта std::vector.

Ни один из методов std::vector (или любой изметоды контейнеров библиотеки C ++) являются потокобезопасными, поэтому весь доступ к ним из всех потоков выполнения должен быть упорядочен (т. е. защищен мьютексом). Не имеет значения, изменено ли содержимое вектора или нет. begin() не является поточно-ориентированным. Полная остановка. Etc ...

Добавляя оскорбление к травме, resize() делает недействительными все существующие итераторы для содержимого std::vector, поэтому resize(), выполняемый любым из std::thread, немедленно аннулируетвсе итераторы одного и того же std::vector объекта, используемого другими потоками;и, следовательно, все это должно быть упорядочено / заблокировано.

TL; DR: вы должны использовать мьютекс для любого доступа к std::vector. Изменено ли содержимое вектора конкретным потоком выполнения, или нет, неважно.

PS Вопрос о том, что std::vector методы не являются потокобезопасными, не зависит от безопасности потока того, что находится ввектор. Если у вас уже есть все итераторы для содержимого вектора, и вы начинаете делать что-то с тем, что находится в векторе, используя существующие итераторы в нескольких потоках выполнения: требуется ли для этого последовательность, зависит от внутренней природы того, что находится в вашем векторе, и еготребования безопасности.

...