Заполнение std :: vector разными потоками - PullRequest
1 голос
/ 03 апреля 2020

Мне нужно заполнить один std :: vector в разных потоках.

Это правильный код? Или я должен добавить мьютекс для моего кода?

void func(int i, std::vector<float>& vec)
{
    vec[i] = i;
}

int main()
{
    std::vector<float> vec(6);
    std::list<std::thread> threads;
    for (int i = 0; i < 6; i++)
    {
        threads.push_back(std::thread(func, i, std::ref(vec)));
    }
    for (auto iter = threads.begin(); iter != threads.end(); iter++)
    {
      (*iter).join();
    }
}

Я проверил мой код, он работает нормально. Есть ли подводные камни? Это потокобезопасный код?

А как насчет получения данных std :: vector разными потоками?

Ответы [ 2 ]

7 голосов
/ 03 апреля 2020

Смежный вопрос:
Является ли std :: vector потокобезопасным и параллельным по умолчанию? Почему или почему нет? .

Это потокобезопасно, потому что вы не изменяете размер вектора и не пытаетесь записать в одну и ту же ячейку памяти в разных потоках.

Кому будущее ответ этот ответ для тех, кто не углубляется в ссылку:

  1. Это не потокобезопасно только , потому что они используют оператор []. Это потокобезопасно, потому что каждый поток явно изменяет другое местоположение в памяти.

  2. Если бы все потоки считывали одно и то же местоположение, используя [], это было бы потокобезопасен.

  3. Если все потоки записывают в одно и то же место, использование [] не помешает им связываться друг с другом.

  4. Я думаю, что если бы это был рабочий код, по крайней мере, был бы вызван комментарий, объясняющий, почему это потокобезопасно. Не уверен ни в каком способе времени компиляции, чтобы кто-то не мог выстрелить себе в ногу, если он изменил эту функцию.


В пункте № 4 мы хотим сообщить будущим пользователям этот код:

  1. Нет, мы не охраняем этот стандартный контейнер библиотеки, хотя это должно быть вашей внутренней реакцией, и
  2. Да, мы проанализировали его, и это безопасно.

Самый простой способ - вставить комментарий, но есть поговорка:

Компилятор не читает комментарии и я тоже.
-Bjarne Страуструп

Я думаю, что какой-то [[attributes]] должен быть способ сделать это? Хотя встроенные , похоже, не поддерживают какие-либо проверки безопасности потоков.


Clang обеспечивает анализ безопасности потоков :

Анализ все еще находится в активной разработке, но он достаточно зрел, чтобы его можно было использовать в промышленных условиях.

Предполагается, что вы реализуете другие функции, для которых требуется std::mutex ответственный за std::vector:

std::mutex _mu;
std::vector<int> _vec GUARDED_BY(_mu);

, тогда вы можете явно добавить атрибут NO_THREAD_SAFETY_ANALYSIS, чтобы отключить проверку безопасности для этой указанной c функции. Я думаю, что лучше всего объединить это с комментарием:

// I know this doesn't look safe but it is as long as
// the caller always launches it with different values of `i`
void foo(int i, std::vector<int>& vec) NO_THREAD_SAFETY_ANALYSIS;

Использование GUARDED_BY говорит мне, что в будущем вы думаете о безопасности потоков. Использование NO_THREAD_SAFETY_ANALYSIS показывает, что вы определили, что эту функцию можно использовать, особенно если другие функции, которые изменяют ваш vector, не отмечены NO_THREAD_SAFETY_ANALYSIS.

2 голосов
/ 03 апреля 2020

Да Это потокобезопасно, потому что вы не записываете в сам векторный объект из разных потоков и не записываете в один и тот же объект в массиве, лежащем в основе вектора.

при создании вектора вы выделяете пространство для 6 элементов и заполните их нулями для типов стручков, таких как int. Эти элементы помещаются в массив, принадлежащий и управляемый вектором, и вектор предоставляет их с помощью итераторов и operator [].

Так что, когда вы редактируете элемент в векторе, вы не редактируете сам вектор, поэтому не нужно защищать вектор мьютексом.

Вам понадобится мьютекс, если вы изменяете сам вектор или один и тот же элемент в разных потоках.

...