Как индекс влияет, когда несколько потоков работают в цикле - PullRequest
0 голосов
/ 23 сентября 2018

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

Ниже приведен код:

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

int nthreads=5;

void *busy(void* c) {

    int my_busy = *(int *) c;

    printf("Hello World with thread index %d\n", my_busy);

    return NULL;
}    

int main()
{
    pthread_t t1[nthreads];
    void* result;

    for(int i=0; i<nthreads; i++)
    {
        pthread_create(&t1[i], NULL, busy, &i);
    }

    for(int i=0; i<nthreads; i++)
    {
        pthread_join(t1[i], &result);
    }
    return 0;
}

Получен вывод:

Hello World with thread index 1
Hello World with thread index 4
Hello World with thread index 2
Hello World with thread index 0
Hello World with thread index 0

Хотя все 5 потоков работают, почему соответствующие индексы не выводятся должным образом?Почему я склонен терять одни индексы и получать другие дважды?Например, в этом случае я потерял 3 и получил 0, выведенный дважды.Хотя использование pthread_join вместе с pthread_create в одном цикле решает проблему, он не планирует параллельное выполнение всех потоков.Что нужно сделать в этом случае, чтобы распечатать все индексы?

Ответы [ 2 ]

0 голосов
/ 24 сентября 2018

Хотя все 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 на всех архитектурах вы можете использовать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 раз.

0 голосов
/ 23 сентября 2018

Поскольку значение индекса уже изменилось при запуске функции «занят».Лучше иметь отдельную копию параметров, которые вы передаете в поток proc.

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