Поскольку эта программа, по-видимому, является всего лишь упражнением в использовании потоков без какой-либо конкретной цели, трудно предложить, как лечить вашу проблему, а не лечить симптом.Я считаю, что на самом деле можно привязать процесс или поток к процессору в Linux, но выполнение этого для всех потоков устраняет большую часть преимуществ использования потоков, и я на самом деле не помню, как это сделать.Вместо этого я собираюсь поговорить о некоторых вещах, которые не так с вашей программой.
Компиляторы C часто делают много предположений, когда проводят оптимизацию.Одно из предположений состоит в том, что, если текущий проверяемый код не выглядит так, как будто он может изменить какую-то переменную, эта переменная не изменится (это очень грубое приближение к этому, и более точное объяснение займет очень много времени).
В этой программе у вас есть переменные, которые используются совместно и изменяются разными потоками.Если переменная читается только потоками (либо const
, либо эффективно const после того, как потоки, которые смотрят на нее) создаются, вам не о чем беспокоиться (и в "чтение по потокам" я включаю основной original thread), поскольку переменная не изменяется, если компилятор генерирует код для чтения этой переменной только один раз (запоминая его в локальной временной переменной) или если он генерирует код для чтения этого значения снова и снова.всегда один и тот же, так что вычисления, основанные на нем, всегда одинаковы.
Чтобы заставить компилятор не делать этого, вы можете использовать ключевое слово volatile
.Он прикрепляется к объявлениям переменных, как ключевое слово const
, и сообщает компилятору, что значение этой переменной может измениться в любой момент, поэтому перечитывайте его каждый раз, когда необходимо его значение, и перезаписывайте его каждый раз, когда для него новое значение
Обратите внимание, что для pthread_mutex_t
(и аналогичных) переменных вам не нужно volatile
.Если бы это было необходимо для типов, которые составляют pthread_mutex_t
, в вашей системе volatile
было бы использовано в определении pthread_mutex_t
.Кроме того, функции, которые обращаются к этому типу, берут его адрес и специально написаны для правильных действий.
Я уверен, что теперь вы думаете, что знаете, как исправить вашу программу, но это не такпросто.Вы делаете математику с общей переменной.Выполнение математических операций над переменной с использованием кода, подобного следующему:
x = x + 1;
, требует, чтобы вы знали старое значение для генерации нового значения.Если x
является глобальным, то вы должны концептуально загрузить x
в регистр, добавить 1 в этот регистр, а затем сохранить это значение обратно вx
.На процессоре RISC вы фактически должны выполнить все 3 из этих инструкций, и, будучи 3-мя инструкциями, я уверен, что вы можете видеть, как другой поток, обращающийся к той же переменной почти в одно и то же время, может закончить сохранением новогозначение в x
сразу после того, как мы прочитали наше значение - делая наше значение старым, поэтому наши вычисления и хранимое нами значение будут неправильными.
Если вам известна какая-либо сборка x86, то вы, вероятно, знаете, что она имеетинструкции, которые могут выполнять математические операции со значениями в ОЗУ (получение и сохранение результата в одном и том же месте ОЗУ в одной инструкции).Вы можете подумать, что эту инструкцию можно использовать для этой операции в системах x86, и вы почти правы.Проблема состоит в том, что эта инструкция все еще выполняется на этапах, на которых будет выполняться инструкция RISC, и есть несколько возможностей для другого процессора изменить эту переменную в то же время, когда мы выполняем ее вычисления.Чтобы обойти это на x86, есть префикс lock
, который может быть применен к некоторым инструкциям x86, и я считаю, что заголовочные файлы glibc включают атомарные макро-функции, чтобы сделать это на архитектурах, которые могут его поддерживать, но это не может быть сделанона всех архитектурах.
Для правильной работы на всех архитектурах вам потребуется:
int local_thread_count;
int create_a_thread;
pthread_mutex_lock(&count_lock);
local_thread_count = num_threads;
if (local_thread_count < MAX_THR) {
num_threads = local_thread_count + 1;
pthread_mutex_unlock(&count_lock);
thread_data_array[local_thread_count].nr = local_thread_count;
/* moved this into the creator
* since getting it in the
* child will likely get the
* wrong value. */
pthread_create(&threads[local_thread_count], NULL, PrintHello,
&thread_data_array[local_thread_count]);
} else {
pthread_mutex_unlock(&count_lock);
}
Теперь, поскольку вы изменили бы num_threads
на volatile
, вы можете атомарно проверить и увеличить число потоков во всех потоках. В конце этого local_thread_count
должен использоваться в качестве индекса в массиве потоков. Обратите внимание, что я создал не один поток в этом коде, а ваш должен был создать несколько. Я сделал это, чтобы сделать пример более понятным, но не должно быть слишком сложно изменить его, чтобы продолжить и добавить NEW_THR
к num_threads
, но если NEW_THR
равно 2 и MAX_THR - num_threads
равно 1 (каким-то образом), то Вы должны как-то справиться с этим правильно.
Теперь, несмотря на все сказанное, может быть другой способ сделать подобные вещи с помощью семафоров. Семафоры похожи на мьютексы, но у них есть счетчик, связанный с ними. Вы не получите значение для использования в качестве индекса в массиве потоков (функция для чтения счетчика семафоров не даст вам этого), но я подумал, что оно заслуживает упоминания, поскольку оно очень похоже.
man 3 semaphore.h
расскажет вам немного об этом.