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

问题描述 投票:0回答:2

我是多线程的新手,一般来说不是 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() 调用但我不希望它休眠,问题就会消失。

感谢任何帮助。

c multithreading operating-system pthreads mutex
2个回答
5
投票

myargs
只存在于创建它的块的末尾。当循环 pass 结束时,它不复存在,访问它是未定义的行为。

由于变量在线程创建后立即不复存在,线程中运行的代码试图在变量不复存在后访问该变量,因此具有未定义的行为。

一个解决方案是延长变量的生命周期。

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
分配结构。

另一个方法是确保线程在继续之前已经获取并复制了数据,这可以通过某种形式的同步来完成。


0
投票

接受的答案非常狭隘地针对问题中的代码;它无法解释问题的实际根本原因,如何自信地识别这 class 错误,或者如何修复它们。

本质上,这是一个数据竞赛错误。你有一个变量

ThreadArgs myargs = {customer, ordered_product, ordered_quantity};

然后您通过引用将此变量传递给线程过程。

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

从你发出这个调用的那一刻起,假设它成功,有两个线程能够读取 和写入 变量。您必须以某种方式确保一个线程对其执行的操作不会与另一个线程对其执行的操作冲突[这是一个官方术语]。

您现在在代码中实际遇到的冲突是父线程 销毁 变量(通过传递给 for 循环的下一次迭代),而没有确保子线程已完成读取第一的。但是,如果变量只是在父线程中overwritten,您的代码也会被破坏,例如如果它的结构是这样的:

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

修复此类错误的一般策略有四种:

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

    请注意,您 not 在这段代码中获取

    clientfd
    的地址,您正在将其 value 转换为
    void *
    以满足预期的参数类型。另请注意,父线程确实 not 关闭 clientfd,除非
    pthread_create
    失败。我们说子线程“拥有”文件描述符——它负责关闭它。线程过程看起来像这样:

    void *handle_client(void *arg) {
        int clientfd = (int)(intptr_t)arg;
        // ... communicate with the client ...
        close(clientfd);
        return 0;
    }
    
  2. 将您要传递给线程的所有数据放在分配有

    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);
    }
    
  3. 如果父线程需要访问传递给每个线程的数据,在该线程退出后,但不需要需要在子线程运行时接触数据,这时候最好将数据放入一个数组,就像你对 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
    
  4. 最后,如果两个或多个线程都在运行时需要访问一个数据结构——如果没有办法安排每个数据在该线程的生命周期内只被一个线程访问——那么你需要使用某种形式的锁定或原子操作。为了完整起见,您可以使用

    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
    }
    

    (为您练习:为什么 不能 使用互斥锁而不是屏障来解决此问题的这个特定实例?)

© www.soinside.com 2019 - 2024. All rights reserved.