代码之家  ›  专栏  ›  技术社区  ›  MoChahadeh

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

  •  0
  • MoChahadeh  · 技术社区  · 3 年前

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

    非常感谢您的帮助。

    1 回复  |  直到 3 年前
        1
  •  5
  •   ikegami Gilles Quénot    3 年前

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

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

        2
  •  0
  •   zwol    3 年前

    公认的答案非常狭隘地针对问题中的代码;它无法解释问题的实际根本原因,以及如何自信地确定这一点 错误,或者如何一般地修复它们。

    从本质上讲,这是一个 data race 程序错误你有一个变量

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

    有四种通用策略可以修复像这样的错误:

    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 在该代码中,您正在转换其 价值 无效* 以便满足期望的参数类型。还要注意,父线程 关闭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
      }
      

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