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