неожиданный результат при типе приведения возвращаемого значения из pthread в C

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

#include <stdio.h>
#include <pthread.h>
#define THREADS 3
#define ITEMS 10

pthread_mutex_t locker = PTHREAD_MUTEX_INITIALIZER;
void* worker(void* arg){
    int id = *(int*)arg;
    int chunk = ITEMS/THREADS;
    int start = chunk * id;
    int end = id == THREADS - 1 ? ITEMS : start + chunk;
    for(int i = start; i < end; i ++){
        //do some work; 
    }
    pthread_mutex_lock(&locker);
    //do some work
    pthread_mutex_unlock(&locker);
    return end - start;  //here return type should be (void *), I casted it to (int).
}

int main(void){
    pthread_t ids[THREADS];
    int args[THREADS];
    for(int i = 0; i < THREADS; i ++){
        args[i] = i;
        pthread_create(ids + i,NULL,worker,&args[i]);
    }

    int total = 0;
    int temp;
    for(int i = 0; i < THREADS; i ++){
        pthread_join(ids[i],&temp); //param here should be (void**), I cast it to (int*)
        total = total +  temp;
        printf("Thread %d process %d items\n",i,temp);
    }

    printf("Get total items:%d\n",total);
}

Я хочу суммировать возвращаемое значение из каждого потока, чтобы проверить, был ли обработан весь элемент. Поскольку я ленив, и это была лишь небольшая практика, я напрямую приводил возвращаемое значение каждого потока из (void *) в (int). Затем я получил некоторый вывод, который меня действительно смутил. Оказывается, я могу успешно прочитать значение из переменной «temp», но когда я попытался сделать

total = total + temp;

значение total не изменилось... Я решил проблему, изменив тип temp с int на long, но я не понимаю, почему здесь важно количество байтов, находящихся в памяти.

Вот пример вывода

Thread 0 process 3 items
Thread 1 process 3 items
Thread 2 process 4 items
Get total items:4

PS: я знаю, как правильно это сделать. Чего я не понимаю, так это того, что происходит, когда я приводил целое число к указателю, а затем сохранял его в 4-байтовой памяти? Почему значение может быть распечатано, но не может быть вычислено с ним? В чем причина, если я использую long (тот же размер, что и void *) вместо int (4 байта памяти), то все работает нормально?


person Xiaowen Hu    schedule 21.05.2017    source источник
comment
эта строка: int temp; должна быть: int *temp = NULL; и эта строка: total = total + temp; лучше было бы написать как: total = total + *temp;   -  person user3629249    schedule 22.05.2017
comment
эта строка: int chunk = ITEMS/THREADS; выполняет целочисленное деление. Это заявление имеет несколько проблем. Одна вопиющая проблема заключается в том, что если THREADS больше, чем ITEMS, то результатом вычисления будет 0. Вероятно, это не то, что вам нужно.   -  person user3629249    schedule 22.05.2017
comment
относительно: return end - start; Существует лучший способ выхода из функций потока. Предложить: int result = end-start; pthread_exit( (void*)&result );   -  person user3629249    schedule 22.05.2017
comment
при вызове: pthread_create() всегда проверяйте возвращаемое значение, чтобы убедиться, что операция прошла успешно. т.е. if( 0 != pthread_create(...)) { perror( pthread_create не удалось); }   -  person user3629249    schedule 22.05.2017


Ответы (2)


Если вы будете осторожны и аккуратно используете uintptr_t из <stdint.h>, вы можете делать то, что хотите, например так:

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

#define THREADS 3
#define ITEMS 10

pthread_mutex_t locker = PTHREAD_MUTEX_INITIALIZER;

static void *worker(void *arg)
{
    int id = *(int *)arg;
    int chunk = ITEMS / THREADS;
    int start = chunk * id;
    int end = id == THREADS - 1 ? ITEMS : start + chunk;
    for (int i = start; i < end; i++)
        printf("A TID %d: s = %2d; e = %2d; i = %2d\n", id, start, end, i);
    pthread_mutex_lock(&locker);
    for (int i = start; i < end; i++)
        printf("B TID %d: s = %2d; e = %2d; i = %2d\n", id, start, end, i);
    pthread_mutex_unlock(&locker);
    return (void *)(uintptr_t)(end - start);
}

int main(void)
{
    pthread_t ids[THREADS];
    int args[THREADS];
    for (int i = 0; i < THREADS; i++)
    {
        args[i] = i;
        pthread_create(ids + i, NULL, worker, &args[i]);
    }

    int total = 0;
    int temp;
    void *vp;
    for (int i = 0; i < THREADS; i++)
    {
        pthread_join(ids[i], &vp); // param here should be (void**), I cast it to (int*)
        temp = (uintptr_t)vp;
        total = total +  temp;
        printf("Thread %d process %2d items (total = %2d)\n", i, temp, total);
    }

    printf("Get total items: %d\n", total);
    return 0;
}

Пример вывода:

A TID 0: s =  0; e =  3; i =  0
A TID 1: s =  3; e =  6; i =  3
A TID 2: s =  6; e = 10; i =  6
A TID 0: s =  0; e =  3; i =  1
A TID 1: s =  3; e =  6; i =  4
A TID 2: s =  6; e = 10; i =  7
A TID 0: s =  0; e =  3; i =  2
A TID 1: s =  3; e =  6; i =  5
A TID 2: s =  6; e = 10; i =  8
B TID 0: s =  0; e =  3; i =  0
A TID 2: s =  6; e = 10; i =  9
B TID 0: s =  0; e =  3; i =  1
B TID 0: s =  0; e =  3; i =  2
B TID 1: s =  3; e =  6; i =  3
B TID 1: s =  3; e =  6; i =  4
B TID 1: s =  3; e =  6; i =  5
Thread 0 process  3 items (total =  3)
B TID 2: s =  6; e = 10; i =  6
B TID 2: s =  6; e = 10; i =  7
B TID 2: s =  6; e = 10; i =  8
B TID 2: s =  6; e = 10; i =  9
Thread 1 process  3 items (total =  6)
Thread 2 process  4 items (total = 10)
Get total items: 10

Обратите внимание, что первый набор выходных данных (помеченный A) чередуется. Второй набор (помеченный B) сериализуется мьютексом. Так получилось, что в этом образце они выполняются в последовательности 0, 1, 2; это была нормальная, но не гарантированная последовательность. Последняя строка A была создана потоком 2, в то время как поток 0 заблокировал мьютекс. Родительский процесс присоединился к потоку 0, пока поток 2 все еще был занят.

Однажды, когда вывод был передан в программу регистрации, я получил:

A TID 0: s =  0; e =  3; i =  0
A TID 2: s =  6; e = 10; i =  6
A TID 1: s =  3; e =  6; i =  3
A TID 0: s =  0; e =  3; i =  1
A TID 2: s =  6; e = 10; i =  7
A TID 1: s =  3; e =  6; i =  4
A TID 0: s =  0; e =  3; i =  2
A TID 2: s =  6; e = 10; i =  8
A TID 1: s =  3; e =  6; i =  5
A TID 2: s =  6; e = 10; i =  9
B TID 0: s =  0; e =  3; i =  0
B TID 0: s =  0; e =  3; i =  1
B TID 0: s =  0; e =  3; i =  2
B TID 1: s =  3; e =  6; i =  3
B TID 1: s =  3; e =  6; i =  4
B TID 1: s =  3; e =  6; i =  5
B TID 2: s =  6; e = 10; i =  6
B TID 2: s =  6; e = 10; i =  7
B TID 2: s =  6; e = 10; i =  8
B TID 2: s =  6; e = 10; i =  9
Thread 0 process  3 items (total =  3)
Thread 1 process  3 items (total =  6)
Thread 2 process  4 items (total = 10)
Get total items: 10
person Jonathan Leffler    schedule 21.05.2017

Вы не можете преобразовать указатель в или из целочисленного типа. Это вызывает неопределенное поведение. Вы также не можете свободно приводить void ** к другому указателю.

Чтобы сделать это правильно, вы должны динамически выделять память в потоке для возвращаемого значения, а затем возвращать указатель на эту память. В основном потоке вам нужно передать адрес void * в pthread_join, а затем скопировать/привести этот указатель.

Итак, ваш поток возвращает это значение следующим образом:

void* worker(void* arg){
    ...
    int *rval = malloc(sizeof(int));
    *rval = end - start;
    return rval;
}

Затем вы получаете это значение следующим образом:

void *vtemp;
int *temp;
for(int i = 0; i < THREADS; i ++){
    pthread_join(ids[i],&vtemp);
    temp = vtemp;   // you can cast to/from a void * to another pointer without a cast
    total = total +  *temp;
    printf("Thread %d process %d items\n",i,*temp);
    free(temp);
}
person dbush    schedule 21.05.2017