【问题标题】:What is the proper way to pass dynamically allocated pointers to pthreads将动态分配的指针传递给 pthread 的正确方法是什么
【发布时间】:2019-04-06 20:02:42
【问题描述】:

我正在编写一个程序,用于使用 pthread 执行矩阵乘法。它通过指定矩阵大小n(假设矩阵为正方形)和线程数p(假设除以n 均匀。对于 A x BA 被水平划分为 p 个段,每个线程接收一个段作为输入,并且整个矩阵 B 并返回结果矩阵 C 的一部分。

我遇到的问题实际上与分配本身无关,而是关于 pthread 的性质的更普遍的问题,我无法找到答案。我会尽量将其剥离。我的矩阵在结构中存储为一维数组。

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;

我正在像这样创建 pthread:

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。我在高处和低处搜索了一些解释,但一无所获。我做错了什么?

【问题讨论】:

  • OT:关于assert(rc == 0); 这只会导致程序(崩溃/退出)并且不应该在生产代码中。建议使用:if( ( rc = pthread_create(&amp;threads[i], NULL, partition_product, (void *)&amp;op) != 0) { perror( "pthread_create failed" ); followed by cleanup activities followed by exit(EXIT_FAILURE); }` perror() 会将您的错误信息和系统认为错误发生的文本原因输出到 stderr 。这对用户来说比通过assert() 使程序崩溃 提供更多信息

标签: c multithreading pointers pthreads


【解决方案1】:

你的问题在这里:

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);

唯一的(小)问题是,在使用该对象完成后,您的子线程现在将负责在它收到的operand_t * 上调用free;否则内存会泄露。

【讨论】:

  • 解决了!非常感谢你,从昨天下午开始,我一直在为此烦恼。我什至从未想过,当孩子访问数据时,父母会覆盖数据。看来我已经习惯了多线程。
猜你喜欢
  • 1970-01-01
  • 2016-12-16
  • 1970-01-01
  • 2013-10-27
  • 2017-01-25
  • 1970-01-01
  • 1970-01-01
  • 2012-02-19
相关资源
最近更新 更多