Простой вопрос POSIX темы - PullRequest
2 голосов
/ 14 мая 2010

У меня есть эта нить POSIX:

void subthread(void)
{
  while(!quit_thread) {

     // do something
     ...

     // don't waste cpu cycles
     if(!quit_thread) usleep(500);
  }

  // free resources
  ...

  // tell main thread we're done
  quit_thread = FALSE;
}

Теперь я хочу завершить subthread () из моего основного потока. Я пробовал следующее:

quit_thread = TRUE;

// wait until subthread() has cleaned its resources
while(quit_thread);

Но это не работает! Предложение while () никогда не завершается, хотя моя дочерняя тема явно устанавливает quit_thread в FALSE после освобождения своих ресурсов!

Если я изменю свой код выключения следующим образом:

quit_thread = TRUE;

// wait until subthread() has cleaned its resources
while(quit_thread) usleep(10);

Тогда все работает нормально! Может кто-нибудь объяснить мне, почему первое решение не работает и почему вдруг работает версия с usleep (10)? Я знаю, что это не очень хорошее решение. Я мог бы использовать семафоры / сигналы для этого, но я хотел бы узнать кое-что о многопоточности, поэтому я хотел бы знать, почему мое первое решение не работает.

Спасибо!

Ответы [ 4 ]

2 голосов
/ 14 мая 2010

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

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

1 голос
/ 14 мая 2010

Скорее всего, это происходит из-за того, что ваш компилятор не знает, что quit_thread может быть изменен другим потоком (потому что C не знает о потоках, по крайней мере, на момент, когда был задан этот вопрос). Из-за этого он оптимизирует цикл while в бесконечный цикл.

Другими словами, он смотрит на этот код:

quit_thread = TRUE;
while(quit_thread);

и думает про себя: «Ха, ничто в этом цикле никогда не может изменить quit_thread на ЛОЖЬ, поэтому кодер, очевидно, просто хотел написать while (TRUE);».

Когда вы добавляете вызов к usleep, компилятор думает об этом и предполагает, что вызов функции может изменить глобальный объект, поэтому он воспроизводит его безопасно и не оптимизирует.

Обычно вы помечаете переменную как volatile, чтобы компилятор не оптимизировал ее, но в этом случае вы должны использовать средства, предоставляемые pthreads, и присоединиться к потоку после установки флага в значение true (и не переустановите подпоток, сделайте это в главном потоке после объединения, если это необходимо). Причина этого заключается в том, что объединение, вероятно, будет более эффективным, чем непрерывный цикл, ожидающий изменения переменной, поскольку поток, выполняющий объединение, скорее всего, не будет выполнен до тех пор, пока соединение не должно быть выполнено.

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

Другими словами, сделайте что-то вроде:

Main thread              Child thread
-------------------      -------------------
fStop = false
start Child              Initialise
Do some other stuff      while not fStop:
fStop = true                 Do what you have to do
                         Finish up and exit
join to Child
Do yet more stuff

И, кроме того, вы должны технически защищать общие переменные с помощью мьютексов, но это один из немногих случаев, когда все в порядке, односторонняя связь, при которой полуизмененные значения переменной не имеют значения (ложь / не- ложь).

Причина, по которой вы обычно защищаете переменную с помощью мьютекса, заключается в том, что один поток не может видеть его в полуизмененном состоянии. Допустим, у вас есть двухбайтовое целое число для подсчета некоторых объектов, и оно установлено на 0x00ff (255).

Допустим, что поток A пытается увеличить этот счетчик, но это не атомарная операция. Он меняет верхний байт на 0x01, но, прежде чем он получит возможность изменить нижний байт на 0x00, поток B подключается и читает его как 0x01ff.

Теперь это не очень хорошо, если поток B захочет что-то сделать с последним элементом, подсчитанным по этому значению. Он должен смотреть на 0x0100, но вместо этого попытаться посмотреть на 0x01ff, эффект которого будет неправильным, если не катастрофическим.

Если бы переменная count была защищена мьютексом, поток B не смотрел бы на нее до тех пор, пока поток A не завершил ее обновление, поэтому никаких проблем не возникло бы.

Причина, по которой однонаправленные логические значения не имеют значения, заключается в том, что любое половинное состояние также будет считаться истинным или ложным, поэтому, если поток А находился посередине между превращением 0x0000 в 0x0001 (только старший байт) поток B все равно будет видеть это как 0x0000 (false) и продолжать (пока поток A не завершит свое обновление в следующий раз).

И если поток A превращает логическое значение в 0xffff, то половина состояния 0xff00 все равно будет считаться истинной для потока B, поэтому он выполнит свою работу до того, как поток A завершит обновление логического значения.

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

1 голос
/ 14 мая 2010

Вы "учите" многопоточность в неправильном направленииПравильный путь - научиться использовать взаимные исключения и условные переменные;любое другое решение потерпит неудачу при некоторых обстоятельствах.

1 голос
/ 14 мая 2010

while(quite_thread); использует значение quit_thread, установленное в строке перед ним. Вызов функции (usleep) побуждает компилятор перезагружать значение в каждом тесте.

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

...