Создание потоков с помощью pthread_create () не работает на моем linux - PullRequest
0 голосов
/ 09 марта 2020

У меня есть этот кусок кода c / c ++:

void * myThreadFun(void *vargp)
{
    int start = atoi((char*)vargp) % nFracK;

    printf("Thread start = %d, dQ = %d\n", start, dQ);

    pthread_mutex_lock(&nItermutex);
    nIter++;
    pthread_mutex_unlock(&nItermutex);
}

void Opt() {

    pthread_t thread[200]; 
    char start[100];

    for(int i = 0; i < 10; i++) {
       sprintf(start, "%d", i);
       int ret = pthread_create (&thread[i], NULL, myThreadFun, (void*) start);
       printf("ret = %d on thread %d\n", ret, i);
    }

    for(int i = 0; i < 10; i++)
       pthread_join(thread[i], NULL);
}

Но он должен создать 10 потоков. Я не понимаю, почему вместо этого создается n <10 потоков. Значение ret всегда равно 0 (10 раз). </p>

1 Ответ

1 голос
/ 11 марта 2020

Но это должно создать 10 потоков. Я не понимаю, почему вместо этого создается n <10 потоков. Значение ret всегда равно 0 (10 раз). </p>

Ваша программа содержит как минимум одну гонку данных, поэтому ее поведение не определено.

Предоставленный источник также неполный, поэтому невозможно быть уверенным, что я могу проверить то же самое, что и вы. Тем не менее, я выполнил минимальное увеличение, необходимое для g++, чтобы скомпилировать его без предупреждений, и проверил, что:

#include <cstdlib>
#include <cstdio>
#include <pthread.h>

pthread_mutex_t nItermutex = PTHREAD_MUTEX_INITIALIZER;
const int nFracK = 100;
const int dQ = 4;
int nIter = 0;

void * myThreadFun(void *vargp)
{
    int start = atoi((char*)vargp) % nFracK;

    printf("Thread start = %d, dQ = %d\n", start, dQ);

    pthread_mutex_lock(&nItermutex);
    nIter++;
    pthread_mutex_unlock(&nItermutex);
    return NULL;
}

void Opt() {

    pthread_t thread[200]; 
    char start[100];

    for(int i = 0; i < 10; i++) {
       sprintf(start, "%d", i);
       int ret = pthread_create (&thread[i], NULL, myThreadFun, (void*) start);
       printf("ret = %d on thread %d\n", ret, i);
    }

    for(int i = 0; i < 10; i++)
       pthread_join(thread[i], NULL);
}

int main(void) {
    Opt();
    return 0;
}

Тот факт, что его поведение не определено, при запуске этой программы на моем Linux machine, это неизменно печатает ровно десять строк "Start thread" , хотя и не все с разными числами. Наиболее правдоподобным выводом является то, что программа действительно запускает десять (дополнительных) потоков, что согласуется с тем фактом, что выходные данные также указывают на то, что каждый вызов pthread_create() указывает на успех, возвращая 0. Поэтому я отвергаю ваше утверждение о том, что меньше фактически запущено более десяти потоков.

Предположительно, последующим вопросом будет то, почему программа не печатает ожидаемый результат, и здесь мы возвращаемся к гонке данных и сопровождающему ее неопределенному поведению. Основной поток записывает текстовое представление переменной итерации i в локальный массив data функции Opt и передает указатель на этот же массив для каждого вызова pthread_create(). Когда он затем возвращается к циклу, чтобы сделать это снова, возникает гонка между вновь созданным потоком, пытающимся считывать данные, и основным потоком, перезаписывающим содержимое массива новыми данными. Я полагаю, что ваша идея состояла в том, чтобы избежать передачи &i, но это ни лучше, ни принципиально не отличается.

У вас есть несколько способов избежать гонки данных в такой ситуации, среди которых выделяются:

  • косвенно инициализирует каждый поток из другого объекта, например:

    int start[10];
    
    for(int i = 0; i < 10; i++) {
        start[i] = i;
        int ret = pthread_create(&thread[i], NULL, myThreadFun, &start[i]);
    }
    

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

  • инициализировать каждый поток напрямую из переданного ему значения. Это не всегда приемлемая альтернатива, но это возможно в этом случае:

    for(int i = 0; i < 10; i++) {
        start[i] = i;
        int ret = pthread_create(&thread[i], NULL, myThreadFun,
                reinterpret_cast<void *>(static_cast<std::intptr_t>(i)));
    }
    

    , сопровождаемый соответствующим кодом в функции потока:

    int start = reinterpret_cast<std::intptr_t>(vargp) % nFracK;
    

    Это довольно распространенная идиома, хотя чаще используется при написании на родном языке pthreads, C, где оно менее многословно.

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

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

...