C多线程|for循环中的线程创建使用上一次迭代中的args

ibps3vxo  于 2023-05-06  发布在  其他
关注(0)|答案(2)|浏览(118)

我是多线程的新手,在C语言中也不是最好的,所以我很简单。
我有一个for循环,它创建了许多线程,我向这些线程传递参数:

for(int i = 0; i < NO_OF_THREADS; i++) {

    int ordered_product = (rand() % NO_OF_PRODUCTS);
    int ordered_quantity = (rand() % 10) + 1;
    int customer = (rand() % NO_OF_CUSTOMERS);

    printf("%d %d %d\n", customer+1, ordered_quantity, ordered_product+1);

    ThreadArgs myargs = {customer, ordered_product, ordered_quantity};

    int rc = pthread_create(&mythreads[i], NULL, thread_function, &myargs);
    if(rc != 0) {
      perror("Pthread create");
      exit(1);
    }

  }

我有一个函数“thread_function”,它是这样写的:

void* thread_function(void* arg) {

  ThreadArgs* args = (ThreadArgs*) arg;
  ThreadArgs myargs = *args;
  int customer_id = myargs.customer_id + 1;
  int product_quantity = myargs.product_quantity;
  int product_id = myargs.product_id +1;

  printf("Customer %d purchased %d of Product %d\n", customer_id, product_quantity, product_id);

  //pthread_exit(NULL)   // I tried this too...
  return NULL;
}

这是我得到的输出:

4 8 4
3 3 9
8 1 9
Customer 8 purchased 1 of Product 9
Customer 8 purchased 1 of Product 9
Customer 8 purchased 1 of Product 9

每个线程都应该打印出其各自的参数,但相反,所有三个线程都在打印最后一次迭代的参数。

由于某种原因,如果我在for循环的底部添加sleep()调用,但我不想让它休眠,问题就会消失。

会感激任何帮助。

tkqqtvp1

tkqqtvp11#

myargs只存在于创建它的块的末尾。当循环结束时,它就不再存在了,访问它是未定义的行为。
由于变量在线程创建后立即停止存在,因此在线程中运行的代码在变量停止存在后尝试访问变量,因此具有未定义的行为。
一个解决方案是延长变量的生存期。

ThreadArgs myargs[ NO_OF_PRODUCTS ];

for ( int i = 0; i < NO_OF_THREADS; ++i ) {
   …
   myargs[i].… = …;
   …
   pthread_create( mythreads+i, NULL, thread_function, myargs+i )
   …
}

另一种方法是使用malloc来分配结构。
另一种方法是确保线程在继续前进之前已经获得并复制了数据,这可以通过某种形式的同步来完成。

vmdwslir

vmdwslir2#

公认的答案是非常狭隘的针对代码的问题;它没有解释问题的真正根本原因,如何自信地识别这类错误,或者如何一般地修复它们。
本质上,这是一个数据竞赛bug。你有一个变量

ThreadArgs myargs = {customer, ordered_product, ordered_quantity};

然后通过引用将这个变量传递给线程过程。

int rc = pthread_create(&mythreads[i], NULL, thread_function, &myargs);

从你进行这个调用的那一刻起,假设它成功了,有两个线程能够读取 * 和写入 * 到变量。你必须以某种方式确保一个线程对它所做的事情不会与另一个线程对它所做的事情发生冲突。
现在,你在代码中实际遇到的冲突是,父线程 * 销毁 * 变量(通过传递给for循环的下一次迭代),而没有确保子线程首先完成从它的阅读。然而,如果变量只是在父线程中被 * 覆盖 *,你的代码也会被破坏,例如。如果它的结构是这样的:

// This code is still wrong
ThreadArgs myargs;
for (int i = 0; i < NO_OF_THREADS; ++i) {
   myargs.ordered_product = (rand() % NO_OF_PRODUCTS);
   myargs.ordered_quantity = (rand() % 10) + 1;
   myargs.customer = (rand() % NO_OF_CUSTOMERS);

   pthread_create(&mythreads[i], NULL, thread_function, &myargs);
}

有四种通用策略来修复这样的bug:
1.如果你只需要传递一个int值,比如文件描述符,或者任何可以在不丢失信息的情况下转换为void *的东西,你可以通过值而不是引用来传递它:

for (;;) {
    int clientfd = accept(listenfd);
    if (clientfd >= 0) {
        pthread_t t;
        int err = pthread_create(&t, create_detached, handle_client,
                                 (void *)(intptr_t)clientfd);
        if (err) {
            log_error(err);
            close(clientfd);
        }
    }
}

请注意,在这段代码中,您 * 没有 * 获取clientfd的地址,而是将其 * 值 * 转换为void *,以满足预期的参数类型。还要注意,父线程不会关闭clientfd,除非pthread_create失败。我们说子线程“拥有”文件描述符--它负责关闭它。线程过程看起来像这样:

void *handle_client(void *arg) {
    int clientfd = (int)(intptr_t)arg;
    // ... communicate with the client ...
    close(clientfd);
    return 0;
}

1.把你要传递给线程的所有数据放在一个分配给malloc的块中;把它释放到线里面。

for (int i = 0; i < NO_OF_THREADS; ++i) {
    ThreadArgs *myargs = malloc(sizeof(ThreadArgs));
    if (!myargs) {
        perror_and_exit("malloc");
    }
    myargs->ordered_product = (rand() % NO_OF_PRODUCTS);
    myargs->ordered_quantity = (rand() % 10) + 1;
    myargs->customer = (rand() % NO_OF_CUSTOMERS);

    pthread_create(&mythreads[i], NULL, thread_function, myargs);
}

1.如果父线程需要访问传递给每个线程的数据,在该线程退出后,但它 * 不 * 需要在子线程运行时接触数据,这时最好像pthread_t句柄那样将数据放入数组中:

pthread_t mythreads[NO_OF_THREADS];
ThreadArgs myargs[NO_OF_THREADS];
for (int i = 0; i < NO_OF_THREADS; ++i) {
    // initialize ThreadArgs[i] here
    pthread_create(&mythreads[i], NULL, thread_function, &myargs[i]);
}
for (int i = 0; i < NO_OF_THREADS; ++i) {
    pthread_join(mythreads[i], NULL);
}
// at this point, and ONLY at this point, it is safe
// for the parent thread to look at mythreads[] again

1.最后,如果需要两个或多个线程在同时运行时访问一个数据结构--如果没有办法安排每个数据片段在该线程的生存期内只由一个线程访问--那么就需要使用某种形式的锁定或原子操作。为了完整起见,下面是如何使用pthread_barrier_t对象来修复问题中的代码:

// this needs to be a global variable
pthread_barrier_t init_barrier;

// for loop in main:
ThreadArgs myargs;
pthread_barrier_init(&init_barrier, NULL, 2);
for (int i = 0; i < NO_OF_THREADS; i++) {
    myargs.ordered_product = (rand() % NO_OF_PRODUCTS);
    myargs.ordered_quantity = (rand() % 10) + 1;
    myargs.customer = (rand() % NO_OF_CUSTOMERS);

    int rc = pthread_create(&mythreads[i], NULL, thread_function, &myargs);
    if (rc != 0) {
        fprintf(stderr, "pthread_create: %s\n", strerror(rc));
        exit(1);
    }
    pthread_barrier_wait(&init_barrier);
}
pthread_barrier_destroy(&init_barrier);

// thread function
void *thread_function(void *arg) {
    ThreadArgs myargs = *(ThreadArgs *)arg;
    pthread_barrier_wait(&init_barrier);
    // can safely access myargs here
}

(为你练习:为什么你不能用互斥体而不是屏障来解决这个问题呢?)

相关问题