C ++ 11 векторный аргумент для потока кажется неинициализированным - PullRequest
0 голосов
/ 08 ноября 2018

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

Однако происходят странные вещи, для которых я не могу найти объяснения, кроме какой-то расы между инициализацией векторов и запуском рабочих потоков.Вот код.

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


class Case {
public:
    int val;
    Case(int i):val(i) {}
};

void
run_thread (std::vector<Case*> &case_list, int idx)
{
    std::cout << "size in thread " << idx <<": " << case_list.size() << '\n';
    for (int i=0; i<10; i++) {
        case_list.push_back(new Case(i));
    }
}

int
main(int argc, char **argv)
{
    int nthrd = 3;
    std::vector<std::thread> threads;
    std::vector<std::vector<Case*>> case_lists;

    for (int i=0; i<nthrd; i++) {
        case_lists.push_back(std::vector<Case*>());
        std::cout << "size of " << i << " in main:" << case_lists[i].size() << '\n';
        threads.push_back( std::thread( run_thread, std::ref(case_lists[i]), i) );
    }

    std::cout << "All threads lauched.\n";

    for (int i=0; i<nthrd; i++) {
        threads[i].join();
        for (const auto cp:case_lists[i]) {
            std::cout << cp->val << '\n';
        }
    }
    return 0;
}

Протестировано на repl.it (gcc 4.6.3), программа выдает следующий результат:

size of 0 in main:0
size of 1 in main:0
size of 2 in main:0
All threads lauched.
size in thread 0: 18446744073705569740
size in thread 2: 0
size in thread 1: 0
terminate called after throwing an instance of 'std::bad_alloc'
  what():  std::bad_alloc
exit status -1 

На моемкомпьютер, кроме чего-то подобного выше, я также получаю:

Segmentation fault (core dumped)

Похоже, что поток 0 получает вектор, который не был инициализирован, хотя вектор кажется должным образом инициализированным в main.

Чтобы изолировать проблему, я попытался перейти на однопоточность, изменив строку:

threads.push_back( std::thread( run_thread, std::ref(case_lists[i]), i) );

на

run_thread(case_lists[i], i);

и закомментировав:

threads[i].join();

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

Мой вопрос: что не так с многопоточной версиейвыше?

1 Ответ

0 голосов
/ 08 ноября 2018

Ссылки (и итераторы) для vector становятся недействительными в любое время, когда изменяется емкость vector. Точные правила перераспределения отличаются в зависимости от реализации, но, по всей вероятности, у вас есть хотя бы одно изменение емкости между первым push_back и последним, и все ссылки, сделанные до этого окончательного увеличения емкости, являются мусором в тот момент, когда это происходит, вызывая неопределенное поведение.

Либо reserve ваш общий vector размер впереди (таким образом, push_back s не вызывает увеличения емкости), инициализируйте весь vector до окончательного размера вперед (так что изменение размера не происходит вообще), или если один цикл заполнен полностью, затем запускают потоки (поэтому все изменения размера происходят до того, как вы извлечете какие-либо ссылки). Простейшим решением было бы инициализировать его до окончательного размера, изменив:

std::vector<std::vector<Case*>> case_lists;

for (int i=0; i<nthrd; i++) {
    case_lists.push_back(std::vector<Case*>());
    std::cout << "size of " << i << " in main:" << case_lists[i].size() << '\n';
    threads.push_back( std::thread( run_thread, std::ref(case_lists[i]), i) );
}

до:

std::vector<std::vector<Case*>> case_lists(nthrd);  // Default initialize nthrd elements up front

for (int i=0; i<nthrd; i++) {
    // No push_back needed
    std::cout << "size of " << i << " in main:" << case_lists[i].size() << '\n';
    threads.push_back( std::thread( run_thread, std::ref(case_lists[i]), i) );
}

Возможно, вы думаете, что vector s будет перераспределяться довольно агрессивно, но по крайней мере на многих популярных компиляторах это не так; gcc и clang следуют строгому шаблону удвоения, поэтому первые три вставки перераспределяют каждый раз (емкость увеличивается от 1, до 2, до 4); ссылка на первый элемент недействительна при вставке второго, а ссылка на второй недействительна при вставке третьего.

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