Неожиданный результат при приведении типа возвращаемого значения из 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 байта памяти), тогда все работало нормально?

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

Вы не можете конвертировать указатель в или из целочисленного типа. Это вызывает неопределенное поведение. Вы также не можете свободно разыграть 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);
}
Другие вопросы по тегам