Потокобезопасно ли читать из элементов индекса нижнего уровня массива структуры, когда он заполняется данными в основном потоке - PullRequest
0 голосов
/ 07 января 2019

Оригинальный вопрос:

Я получил массив структур и заполнил его в отдельном потоке, читая его в основном потоке:

struct DataModel MyData[1024];

struct DataModel
{
    bool IsFilled;
    float a;
    float b;
}
  • У меня есть поток, который заполняет массив Mydata от 0 индекса до последнего индекса (выше это 1024).

  • Затем я получаю последний заполненный структурный индекс из потока заполнения.

  • А потом я пытаюсь прочитать значения элемента с индексом ниже заполненного.

  • Предположим, что когда 500-й элемент заполнен, я считал значение из элемента 499 массива MyData, поэтому я заверяю, что я не читаю записываемый элемент массива.

Q1: Безопасен ли этот поток?

Q2: есть ли вероятность возникновения неопределенного поведения или неправильного чтения вейлов?


Дополнительные правки:

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

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

std::atomic<int> FilledIndex;
    void FillingMyData(struct DataModel myData[])
    {
      for(size_t i = 0; i < 1024; i++)
      {
        myData[i].a = rand();
        myData[i].b = rand();
        myData[i].IsFilled = true;

    FilledIndex = i;
  }
}

int main()
{
     std::thread ReadThread(FillingMyData, MyData);
     while(FilledIndex < 1024)
     {
          std::cout << MyData[FilledIndex].a;
     }
     ReadThread.join();
     return 0;
}

Ответы [ 4 ]

0 голосов
/ 07 января 2019

Я думаю, что этот код определенно не является потокобезопасным.

Прежде всего, переменная FilledIndex не инициализируется: как указано cplusplus.com , если вы не отправляете значение конструктору, атомарная переменная остается в неинициализированном состоянии. Это может привести к неожиданному поведению.

Другая проблема связана с условием выхода в главном потоке, поскольку оператор for в ReadThread повторяется до 1023, поэтому FilledIndex никогда не примет значение 1024 и основной поток никогда не завершится.

Но главная проблема заключается в непредсказуемости планирования ваших потоков: что гарантирует выполнение ReadThread после основного? Ничего такого!

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

Например, если мы назовем ReadThread как T , основной поток как M и массив как A , это возможные расписания (при условии A размера 5 для простоты):

  • T T T M T на выходе будет A [2]
  • М М Т М Т выход будет A [0] A [0] A [1]

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

Я надеюсь, что вы поймете, что я пытаюсь сказать. По любым вопросам или разъяснениям, очевидно, я здесь! Я отвечу как можно скорее!

0 голосов
/ 07 января 2019

Код в том виде, в котором он написан, не обязательно безопасен и может не принести ничего полезного.

  • начальное значение FilledIndex равно нулю, поэтому он может читать данные с нулевого индекса перед записью, включая, возможно, частично записанные значения. Возможно, вы захотите установить его в -1 и подождать, пока он будет> = 0, прежде чем выводить.
  • ничто не мешает основному потоку выполняться бесконечно, выводя одно и то же значение на ноль навсегда - это зависит от планировщика; Также нет ничего, что могло бы остановить основной поток, выводящий значение по заданному индексу более одного раза. Вы, вероятно, хотите считать от нуля до заполненного индекса, а не выводить значение по заполненному индексу.

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

0 голосов
/ 07 января 2019

Код имеет гонку данных и будет продолжать цикл навсегда.

Гонка данных происходит потому, что начальное значение FilledIndex равно 0, поэтому при первом цикле вы читаете из того же индекса, в который записываете (потому что i == 0).

Цикл никогда не закончится, потому что i не будет никогда достигнет конечного значения - цикл завершится до установки FilledIndex в 1024.

0 голосов
/ 07 января 2019

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

Что здесь может произойти, так это то, что называется ложное разделение . В этих случаях происходит то, что отдельные объекты находятся в одной строке кэша в памяти. Когда в ядре / потоке изменяется один объект, эта строка кэша помечается как измененная. Это означает, что другое ядро ​​/ поток должен повторно синхронизировать эту линию, чтобы внести какие-либо изменения, что означает, что оба ядра / потока не могут работать одновременно. Это только снижение производительности, и программа все равно даст правильные результаты, но будет медленнее.

...