代码之家  ›  专栏  ›  技术社区  ›  Adam Rutledge

让父线程在初始化子线程pthread后等待,直到它从子线程接收到一些信号的最佳方法是什么?

  •  -4
  • Adam Rutledge  · 技术社区  · 5 月前

    在使用pthread_create生成线程后,我希望父线程等待任意时间,直到子线程允许它继续。以下是我使用互斥体的方法:

    pthread_mutex_t childLock;
    
    void *childProcess()
    {
        pthread_mutex_lock(&childLock);
        // do important initialization
        pthread_mutex_unlock(&childLock);
        // do some parallel processing
    }
    
    int main()
    {
        pthread_t childThread;
        pthread_create(&childThread, 0, childProcess);
        pthread_mutex_lock(&childLock); // wait here for child initialization
        // do some parallel processing
        pthread_join(childThread, 0);
        return 0;
    }
    

    这里的问题是,不能保证childProcess会首先获得互斥体,因此行为是未定义的。我可以想出几种方法来解决这个问题,但我对其中任何一种都不满意:

    1. 在获取互斥体之前,先在主线程中休眠。我认为从技术上讲,这并不能保证孩子会赢得比赛,如果可能的话,我想避免睡在任何一条线上。
    2. 丢弃互斥对象,并在while循环中等待子对象更新信号变量。如果没有必要,我不想浪费宝贵的时间忙于等待。
    3. 将互斥体所有权从父级转移到子级?我不确定这是否可能,这似乎有点邪恶,但它可以在不睡觉或忙于等待的情况下解决问题。这个想法是在main中锁定互斥体,然后在生成线程时将锁转移到子线程中,以确保它首先拥有锁。

    还有其他选择吗?该解决方案只需要与Linux和GCC一起工作。

    2 回复  |  直到 5 月前
        1
  •  2
  •   John Bollinger    5 月前
    1. 在获取互斥体之前,先在主线程中休眠。我认为从技术上讲,这并不能保证孩子会赢 种族,如果可以的话,我想避免睡在任何一条线上。

    一方面,这确实不能保证新线程赢得比赛,另一方面,它确实可能使主线程的延迟时间比它需要的要长得多。 sleep() 不是同步或IPC设备。

    1. 丢弃互斥对象,并在while循环中等待子对象更新信号变量。我不想浪费宝贵的时间忙于等待 如果我不需要的话。

    只要使用原子信号变量,这实际上是可行的。C自C99以来就内置了这些功能,但我不确定C++03是否有。

    1. 将互斥体所有权从父级转移到子级?我不确定这是否可能

    这是不可能的。


    还有其他选择吗?该解决方案只需要与Linux和GCC一起工作。

    最简单的方法可能是使用信号量。 主线程用值0初始化它。启动另一个线程后,主线程尝试递减信号量,直到子线程递增信号量为止(如果它还没有这样做)。

    这只需要设置一个同步对象,而不是两个(mutex+cv),而且使用习惯比mutex+cv更简单。

    此外,这可以在不改变启动多个线程的情况下进行自适应(互斥体+cv也可以),因为在初始线程重新获得控制权后,信号量已经为下一个周期做好了准备。

    示例代码:

    sem_t thread_start_lock;
    
    void *thread(void *arg) {
        // do something ...
    
        sem_post(&thread_start_lock);  // probably abort() if this fails
    
        // ...
    }
    
    int main(void) {
       sem_init(&thread_start_lock, 0, 0);  // probably terminate if this fails
    
       pthread_t thread_id;
       pthread_create(&thread_id, NULL, thread, NULL);
       sem_wait(&thread_start_lock);
    
       // ...
    }
    

    当然,为了增强健壮性,您需要检查返回代码并处理错误。代码中包含了一些关于这一点的注释,但需要检查所有提供的函数调用,而不仅仅是带注释的函数调用。

        2
  •  1
  •   Remy Lebeau    5 月前

    如果你必须只使用pthreads,那么pthreads提供了一个 条件变量 API,您可以使用它使父级等待来自子级的信号,例如:

    #include <pthread.h>
    
    struct sChildInit
    {
        pthread_cond_t cv;
        pthread_mutex_t mtx;
    };
    
    void* childProcess(void *arg)
    {
        sChildInit *init = (sChildInit*) arg;
    
        pthread_mutex_lock(&init->mtx);
        // do important initialization
        pthread_cond_signal(&init->cv);
        pthread_mutex_unlock(&init->mtx);
    
        // do some parallel processing
    
        return NULL;
    }
    
    int main()
    {
        sChildInit init;
        pthread_cond_init(&init.cv, NULL);
        pthread_mutex_init(&init.mtx, NULL);
    
        pthread_t childThread;
        pthread_create(&childThread, NULL, childProcess, &init);
    
        // wait here for child initialization
        pthread_mutex_lock(&init.mtx);
        pthread_cond_wait(&init.cv, &init.mtx);
        pthread_mutex_unlock(&init.mtx);
    
        // do some parallel processing
    
        pthread_join(childThread, 0);
        pthread_cond_destroy(&init.cv);
        pthread_mutex_destroy(&init.mtx);
    
        return 0;
    }
    

    如果您可以使用C++11或更高版本,请使用 std::condition_variable (以及 std::thread )相反,例如:

    #include <condition_variable>
    #include <mutex>
    #include <thread>
            
    struct sChildInit
    {
        std::conditional_variable cv;
        std::mutex mtx;
    };
    
    void childProcess(sChildInit &init)
    {
        {
        std::lock_guard<std::mutex> lk(init.mtx);
        // do important initialization
        init.cv.notify_one();
        }
    
        // do some parallel processing
    }
    
    int main()
    {
        sChildInit init;
        std::thread childThread(&childProcess, std::ref(init));
    
        // wait here for child initialization
        {
        std::unique_lock<std::mutex> lk(init.mtx);
        init.cv.wait(lk);
        }
    
        // do some parallel processing
    
        childThread.join();
        return 0;
    }