Хотя все 5 потоков работают, почему соответствующие индексы не выводятся должным образом?
Вы передаете указатель на переменную каждому потоку и одновременно изменяете эту переменную потокафункции доступа к нему.Почему вы ожидаете, что функции потока увидят какое-то конкретное значение? Они работают одновременно .Потоки могут видеть совершенно невозможное искаженное значение, если один поток читает значение, в то время как другой изменяет его, на некоторых архитектурах.
Например, в этом случае я потерял 3и получил 0, выведенный дважды.
Хотя машинный код, сгенерированный, например, GCC, увеличивает переменную, к которой обращаются функции потока после создания каждой функции потока, значение, наблюдаемое функциями потока, может быть "старым" внекоторые архитектуры, потому что не используются барьеры или синхронизация.
Будет ли это происходить на вашей конкретной машине (без явных барьеров или синхронизации), зависит от того, какую модель упорядочения памяти реализует ваша машина.
Например, в x86-64 (он же AMD64; 64-битная архитектура Intel / AMD) все операции чтения и записи выполняются по порядку, , за исключением того, что магазины могут быть упорядочены после загрузки * 1021.*.Это означает, что если первоначально сказать i = 0;
, а поток A делает i = 1;
, поток B все еще может видеть i == 0
даже после того, как поток A изменил переменную.
Обратите внимание, что при добавлении барьеров (например, _mm_fence()
)использование встроенных x86 / AMD64, предоставляемых <immintrin.h>
при использовании большинства компиляторов Си), недостаточно для обеспечения того, чтобы каждый поток видел уникальное значение, потому что начало каждого потока может быть отложено относительно момента реального мира, когда вызывался pthread_create()
,Все, что они гарантируют, - то, что самое большее один поток видит нулевое значение.Два потока могут видеть значение 1, три значения 2 и т. Д .;даже все потоки могут видеть значение 5.
Что нужно сделать в этом случае, чтобы распечатать все индексы?
Самый простой вариант - предоставитьиндекс, который будет напечатан как значение, а не как указатель на переменную.В busy () используйте
my_busy = (int)(intptr_t)c;
, а в main ()
pthread_create(&t1[i], NULL, busy, (void *)(intptr_t)i);
Тип intptr_t
является целочисленным типом со знаком, способным содержать указатель, и определяется в<stdint.h>
(обычно включается вместо включения <inttypes.h>
).
(Поскольку вопрос помечен linux , я, вероятно, должен указать, что в Linux на всех архитектурах вы можете использоватьlong
вместо intptr_t
и unsigned long
вместо uintptr_t
. Представления ловушек в long
или unsigned long
отсутствуют, и каждое возможное значение long
/ unsigned long
может быть преобразовано вуникальный void *
, и наоборот; гарантируется правильная работа в обоих направлениях. Интерфейс системного вызова ядра требует этого, поэтому в будущем он также вряд ли изменится.)
Если вынужно передать указатель на i
, но чтобы каждый поток видел уникальное значение, вам нужно использовать какую-то синхронизацию.
Самый простой синхронизированный подход - использовать семафор.Вы можете сделать его глобальным, но используя структуру для описания рабочих параметров и передав указатель структуры (даже если один и тот же используется для всех рабочих потоков), он более устойчив:
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <stdio.h>
#define NTHREADS 5
struct work {
int i;
sem_t s;
};
void *worker(void *data)
{
struct work *const w = data;
int i;
/* Obtain a copy of the value. */
i = w->i;
/* Let others know we have copied the value. */
sem_post(&w->s);
/* Do the work. */
printf("i == %d\n", i);
fflush(stdout);
return NULL;
}
int main()
{
pthread_t thread[NTHREADS];
struct work w;
int rc, i;
/* Initialize the semaphore. */
sem_init(&w.s, 0, 0);
/* Create the threads. */
for (i = 0; i < NTHREADS; i++) {
/* Create the thread. */
w.i = i;
rc = pthread_create(&thread[i], NULL, worker, &w);
if (rc) {
fprintf(stderr, "Failed to create thread %d: %s.\n", i, strerror(rc));
exit(EXIT_FAILURE);
}
/* Wait for the thread function to grab its copy. */
sem_wait(&w.s);
}
/* Reap the threads. */
for (i = 0; i < NTHREADS; i++) {
pthread_join(thread[i], NULL);
}
/* Done. */
return EXIT_SUCCESS;
}
, посколькуОсновной поток, поток, который изменяет значение, видимое каждым рабочим потоком, участвует в синхронизации, так что каждая рабочая функция считывает значение до создания следующего потока, выходные данные всегда будут в порядке возрастания i
.
Гораздо лучший подход - создать рабочий пул , в котором основной поток определяет работу, выполняемую совместно потоками, а функции потоков просто получают следующий кусок работы.в любом порядке:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <limits.h>
#include <stdio.h>
#include <errno.h>
#define NTHREADS 5
#define LOOPS 3
struct work {
pthread_mutex_t lock;
int i;
};
void *worker(void *data)
{
struct work *const w = data;
int n, i;
for (n = 0; n < LOOPS; n++) {
/* Grab next piece of work. */
pthread_mutex_lock(&w->lock);
i = w->i;
w->i++;
pthread_mutex_unlock(&w->lock);
/* Display the work */
printf("i == %d, n == %d\n", i, n);
fflush(stdout);
}
return NULL;
}
int main(void)
{
pthread_attr_t attrs;
pthread_t thread[NTHREADS];
struct work w;
int i, rc;
/* Create the work set. */
pthread_mutex_init(&w.lock, NULL);
w.i = 0;
/* Thread workers don't need a lot of stack. */
pthread_attr_init(&attrs);
pthread_attr_setstacksize(&attrs, 2 * PTHREAD_STACK_MIN);
/* Create the threads. */
for (i = 0; i < NTHREADS; i++) {
rc = pthread_create(thread + i, &attrs, worker, &w);
if (rc != 0) {
fprintf(stderr, "Error creating thread %d of %d: %s.\n", i + 1, NTHREADS, strerror(rc));
exit(EXIT_FAILURE);
}
}
/* The thread attribute set is no longer needed. */
pthread_attr_destroy(&attrs);
/* Reap the threads. */
for (i = 0; i < NTHREADS; i++) {
pthread_join(thread[i], NULL);
}
/* All done. */
return EXIT_SUCCESS;
}
Если вы скомпилируете и запустите этот последний пример, вы заметите, что выходные данные могут быть в нечетном порядке, но каждый i
уникален, а каждый *С 1077 * по n = LOOPS-1
происходит ровно NTHREADS
раз.