Как правильно передавать динамически размещенные указатели в pthreads? - PullRequest
1 голос
/ 06 апреля 2019

Я пишу программу для назначения для выполнения умножения матриц с использованием pthreads.Он работает путем указания размера матрицы n (матрицы считаются квадратными) и количества потоков p , которое предполагается делить n равномерно.Для A x B , A горизонтально разделено на сегменты p , и каждый поток получает в качестве входных данных отдельный сегмент и всю матрицу B и возвращает часть полученной матрицы C .

Проблема, с которой я столкнулся, заключается не в самом назначении, а в более общей проблеме природы потоков, на которую я не смог найти ответ.Я постараюсь раздеть это как можно больше.Мои матрицы хранятся в виде 1D-массивов внутри структуры.

typedef struct matrix {
    int *matrix;
    int size;
} matrix_t

Они распределяются примерно так

matrix_t mtx = {
    malloc(input_size * input_size * sizeof(int)),
    input_size
};

и заполняются случайным образом функцией.Разделы хранятся в двумерном массиве, адрес которого возвращается из функции, но распределяется следующим образом:

int **partitions = partitionmtx(mtx, num_threads);

int **partitionmtx(matrix_t mtx, int threads) 
{
    int partlen = mtx.size * (mtx.size / threads);
    int **parts = malloc(threads * sizeof(int));

    for(int i = 0; i < threads; ++i) {
        parts[i] = malloc(partlen * sizeof(int));
        // partitions populated...
    }

    return parts;
}

. Это прекрасно работает.Проблема возникает, когда я отправляю каждый раздел в поток.Для того, чтобы аргументы потока были простыми, я связал их так:

typedef struct operand {
    matrix_t matrix;
    int *partition;
    int partition_length;
} operand_t;

Я создаю pthreads примерно так:

pthread_t threads[num_threads];
pthread_mutex_init(&mymutex, NULL);
int rc;

for(int i = 0; i < num_threads; ++i) {
    operand_t op = {matrix, partitions[i], partition_length};
    rc = pthread_create(&threads[i], NULL, partition_product, (void *)&op);
    assert(rc == 0);
}

for(int i = 0; i < num_threads; ++i) {
    rc = pthread_join(threads[i], NULL);
    assert(rc == 0);
}

Переходя к моемуФункция partition_product.Моим первым приоритетом было, очевидно, убедиться, что каждый поток получал правильные данные, поэтому я напечатал, как выглядит каждый поток, так:

void* partition_product(void *args)
{
    operand_t *op = (operand_t *)args;

    pthread_mutex_lock(&mymutex);

    printf("Matrix:\n);
    printmtx(op->matrix); // This is a function I defined but its details aren't relevant here
    printf("\nPartition:" );
    for(int i = 0; i < op->partition_length; ++i)
        printf("%4d", op->partition[i]);

    pthread_mutex_unlock(&mymutex);
}

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

./threadmatrix -n 4 -p 4

, напечатали один и тот же раздел.Я думал, что это могло быть побочным эффектом печати из нитей, отсюда и блокировка мьютекса на отпечатках.Затем я подумал напечатать адрес каждого раздела [i] в ​​исходном потоке и в созданных потоках, чтобы увидеть, что происходит, и кажется, что каждый поток получает один и тот же адрес с момента создания.Я получаю данные в потоки и, похоже, могу манипулировать ими без проблем, но это все те же данные.В частности, они всегда получают адрес последнего раздела.Я испробовал всю хорошую практику работы с указателями, которую я знаю, и все же, если у partitions [i] есть адрес 0x00007ffffde234, скажем, тогда все 4 потока из указанного выше адреса печати вызывают 0x00007ffffde234.Я искал высоко и низко какое-то объяснение и ничего не нашел.Что я делаю не так?

1 Ответ

3 голосов
/ 06 апреля 2019

Ваша проблема здесь:

operand_t op = {matrix, partitions[i], partition_length};
rc = pthread_create(&threads[i], NULL, partition_product, (void *)&op);

Обратите внимание, что указатель, который вы передаете в последнем аргументе, является указателем на op, который находится в стеке. Проблема в том, что, как только основной поток завершит свою итерацию цикла for, op будет уничтожен, а затем воссоздан для следующей итерации цикла; Это означает, что позже, когда дочерний поток начнет работать и попытаться использовать этот аргумент-указатель, operand_t, на который указывает указатель, больше не будет действительным. (В вашем случае одна и та же ячейка стековой памяти используется повторно для всех созданных дочерних потоков, что частично объясняет поведение, которое вы видите)

Чтобы избежать этой проблемы, вам нужно убедиться, что время жизни объекта, на который вы передаете указатель, достаточно велико, чтобы объект все еще оставался действительным, когда дочерний поток разыменовывает указатель для чтения полей объекта. Самый простой способ сделать это - разместить объект в куче, вместо этого:

operand_t * op = (operand_t *) malloc(sizeof(operand_t));
op->matrix = matrix;
op->partition = partitions[i];
op->partition_length = partition_length;
rc = pthread_create(&threads[i], NULL, partition_product, (void *)op);

Единственный (маленький) улов - то, что ваш дочерний поток теперь будет отвечать за вызов free на operand_t *, который он получает, после того, как это сделано с использованием этого объекта; в противном случае память будет утечка.

...