【问题标题】:how to wait for starting thread to have executed init code如何等待启动线程执行初始化代码
【发布时间】:2012-07-13 09:20:51
【问题描述】:

我在将主线程同步到最近启动的子线程时遇到问题。

我想做的是:

  • 主线程创建一个新的子线程并阻塞
  • 子线程启动并初始化(可能需要一些时间)
  • 一旦子线程被初始化,主线程就会继续(并且两个线程并行运行)

我的第一次尝试是这样的:

  typedef struct threaddata_ {
    int running;
  } threaddata_t;

  void*child_thread(void*arg) {
    threaddata_t*x=(threaddata_t)arg;
    /* ... INITIALIZE ... */
    x->running=1; /* signal that we are running */

    /* CHILD THREAD BODY */

    return 0;
  }

  void start_thread(void) {
    threaddata_t*x=(threaddata_t*)malloc(sizeof(threaddata_t));
    x->running=0;
    int result=pthread_create(&threadid, 0, child_thread, &running);
    while(!x->running) usleep(100); /* wait till child is initialized */

    /* MAIN THREAD BODY */
  }

现在我根本不喜欢这个,因为它迫使主线程睡眠的时间可能比必要的更长。 所以我做了第二次尝试,使用互斥锁和条件

  typedef struct threaddata_ {
    pthread_mutex_t x_mutex;
    pthread_cond_t  x_cond;
  } threaddata_t;

  void*child_thread(void*arg) {
    threaddata_t*x=(threaddata_t)arg;
    /* ... INITIALIZE ... */

    pthread_cond_signal(&x->x_cond); /* signal that we are running */

    /* CHILD THREAD BODY */

    return 0;
  }

  void start_thread(void) {
    threaddata_t*x=(threaddata_t*)malloc(sizeof(threaddata_t));
    pthread_mutex_init(&x->x_mutex, 0);
    pthread_cond_init (&x->x_cond , 0);

    pthread_mutex_lock(&x->x_mutex);
    int result=pthread_create(&threadid, 0, child_thread, &running);
    if(!result)pthread_cond_wait(&x->x_cond, &x->x_mutex);
    pthread_mutex_unlock(&x->x_mutex);

    /* MAIN THREAD BODY */
  }

这似乎比第一次尝试更理智(使用正确的信号而不是滚动我自己的等待循环),直到我发现这包括一个竞争条件: 如果子线程已经足够快地完成初始化(在主线程等待条件之前),它将死锁主线程。

我想我的情况并不少见,所以一定有一个非常简单的解决方案,但我现在看不到。

【问题讨论】:

    标签: c multithreading synchronization pthreads


    【解决方案1】:

    屏障应该可以很好地解决问题。由于您提到在 cmets 中需要支持 Win32,因此最新的 Win32 pthread 支持屏障,因此您不必编写自己的包装器来获得 Win32 和 *nix 之间的一些可移植性。

    类似:

    typedef struct threaddata_ {
        pthread_barrier_t* pbarrier;
    } threaddata_t;
    
    void* child_thread(void*arg) {
        threaddata_t*x=(threaddata_t*)arg;
        /* ... INITIALIZE ... */
    
        int result = pthread_barrier_wait(x->pbarrier);
    
        /* CHILD THREAD BODY */
    
        return 0;
    }
    
    void start_thread(void) {
        pthread_barrier_t barrier;
        int result = pthread_barrier_init(&barrier, NULL, 2);
    
        threaddata_t*x=(threaddata_t*)malloc(sizeof(threaddata_t));
        x->pbarrier = &barrier;
    
        int result=pthread_create(&threadid, 0, child_thread, &x);
    
        result = pthread_barrier_wait(&barrier);
        /* child has reached the barrier */
    
        pthread_barrier_destroy(&barrier);  /* note: the child thread should not use    */
                                            /* the barrier pointer after it returns from*/
                                            /* pthread_barrier_wait()                   */
    
    
        /* MAIN THREAD BODY */
    }
    

    此解决方案的缺点是它可能会不必要地暂时阻塞子线程。如果这是个问题,condition variable solution mentioned by Dmitry Poroh 是最好的选择。

    【讨论】:

      【解决方案2】:

      condvar/mutex 对的正确使用方式:

      bool initialised = false;
      mutex mt;
      convar cv;
      
      void *thread_proc(void *)
      {
         ...
         mt.lock();
         initialised = true;
         cv.signal();
         mt.unlock();
      }
      
      int main()
      {
         ...
         mt.lock();
         while(!initialised) cv.wait(mt);
         mt.unlock();
      }
      

      该算法避免了任何可能的竞争。您可以使用互斥锁锁定时修改的任何复杂条件(而不是简单的 !initialised)。

      【讨论】:

      • +1:还用于为while() 循环添加防止虚假唤醒的保护。
      • 请注意,这与 OP 的两种方法的组合完全相同(第一种方法中的 usleep()pthread_cond_wait() 取代)。
      • 请注意,第一种方法中的运行标志不受互斥锁的保护。
      • 嗯,是的,这是 OP 两种方法的正确组合;)
      【解决方案3】:

      正确的工具是sem_tmain 线程将使用0 初始化它们,并等待它从新启动的线程接收到令牌。

      顺便说一句,您的 mutex/cond 解决方案存在竞争条件,因为子线程没有锁定 mutex。

      【讨论】:

      • 我会研究互斥锁,但在发布后我也注意到了竞争条件的原因。我是否正确理解将 pthread_cond_signal() 的调用与我的互斥锁的锁定/解锁(在调用 pthread_create 之前被锁定)相结合就足够了?我认为它应该并且不需要@dmitry-poroh 提出的“while(!initialized)wait()”循环
      • 似乎 sem_t 在 pthread 的某些 w32 实现上不可用(至少我必须使用的实现)。该代码应该在包括 w32 在内的各种系统上运行,这几乎不包括该解决方案,尽管它确实似乎是最优雅的一个。
      • 是的,因为你在启动线程之前拿了互斥锁,所以线程只能在主线程处于等待状态时锁定互斥锁。可惜周围还有没有sem_t的pthread实现。
      • Windows 有自己的信号量实现。您可以围绕它编写一个包装器,如果可用,则使用 pthread 信号量,如果不可用,则使用 Windows 信号量。
      • +1。可能使用 sem_t 的集合点比屏障或 condvar/mutex 组合更适合这个问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-12-05
      • 2018-12-15
      • 1970-01-01
      • 2017-04-29
      • 2021-10-01
      • 2011-04-10
      • 2021-07-25
      相关资源
      最近更新 更多