C++ PThread初学者-启动、同步、停止工作线程

C++ PThread初学者-启动、同步、停止工作线程,c++,synchronization,pthreads,backgroundworker,C++,Synchronization,Pthreads,Backgroundworker,我有以下管理人员情况: class Manager { private: pthread_attr_t workerSettings; pthread_t worker; pthread_cond_t condition; pthread_mutex_t mutex; bool workerRunning; static void* worker_function(void* args) { Manager* manager =

我有以下管理人员情况:

class Manager {
private:
    pthread_attr_t workerSettings;
    pthread_t worker;
    pthread_cond_t condition;
    pthread_mutex_t mutex;
    bool workerRunning;

    static void* worker_function(void* args) {
        Manager* manager = (Manager*)args;

        while(true) {
            while(true) {
                pthread_mutex_lock(&manager->mutex);
                if(/* new data available */)
                {
                    /* copy new data from shared to thread memory */
                    pthread_mutex_unlock(&manager->mutex);
                }
                else
                {
                    pthread_mutex_unlock(&manager->mutex);
                    break;
                }

                /* process the data in thread memory */

                pthread_mutex_lock(&manager->mutex);
                /* copy results back to shared memory */
                pthread_mutex_unlock(&manager->mutex);
            }

            pthread_mutex_lock(&manager->mutex);

            // wait for new data to arrive
            while(manager->workerRunning && !/* new data available*/)
                pthread_cond_wait(&manager->condition, &manager->mutex);

            // check if we should continue running
            if(!manager->workerRunning)
            {
                pthread_mutex_unlock(&manager->mutex);
                break;
            }

            pthread_mutex_unlock(&manager->mutex);
        }

        pthread_exit(NULL);
        return NULL; // just to avoid the missing return statement compiler warning
    }

public:
    Manager() : workerRunning(true) {
        pthread_cond_init(&condition, NULL);
        pthread_mutex_init(&mutex, NULL);
        pthread_attr_init(&workerSettings);
        pthread_attr_setdetachstate(&workerSettings, PTHREAD_CREATE_JOINABLE);
        pthread_create(&worker, &workerSettings, worker_function, (void*)this);
    }

    // this *may* be called repeatedly or very seldom
    void addData(void) {
        pthread_mutex_lock(&mutex);
        /* copy new data into shared memory */
        pthread_cond_signal(&condition);
        pthread_mutex_unlock(&mutex);
    }

    ~Manager()
    {
        // set workerRunning to false and signal the worker
        pthread_mutex_lock(&mutex);
        workerRunning = false;
        pthread_cond_signal(&condition);
        pthread_mutex_unlock(&mutex);

        // wait for the worker to exit
        pthread_join(worker, NULL);

        // cleanup
        pthread_attr_destroy(&workerSettings);
        pthread_mutex_destroy(&mutex);
        pthread_cond_destroy(&condition);
    }
};
在几个地方,我不完全确定这一点:

  • Manager在其构造函数中生成新线程这一事实是否被认为是一种不好的做法?(我将只有一个管理器对象,因此我想这应该可以)
  • 关于pthread_退出,我在很多教程中都看到了,但我不太明白为什么它会出现?我不能简单地返回函数以退出线程吗?我还认为返回NULL是死代码,但gcc在它丢失时发出警告,因为它显然无法知道pthread_exit在该点上已经杀死了线程
  • 关于构造函数-我可以在生成线程后立即销毁thread attr对象(workerSettings),还是必须在线程的整个生命周期内保持有效
  • 关于析构函数:这是正确的方法吗
最重要的是:

  • 您有经验的眼睛是否看到任何同步问题
谢谢你的帮助

你问

Manager在其构造函数中生成新线程这一事实是否被认为是一种不好的做法

在大多数情况下,RAII足以处理对象创建和资源获取。在某些情况下,您可能希望实现延迟的资源初始化:当您首先构造一个对象,然后继续初始化时。例如,可以通过ctor(默认或参数化)和打开/启动例程来实现。尽管您也可以在ctor中执行此操作,并通过在进程堆中分配对象(通过新操作符)来实现差异对象的创建。这取决于您的需求、软件设计考虑因素和公司软件开发标准。 因此,您可以在ctor中创建线程,也可以希望或需要在应用程序/对象生命周期的后期生成线程

pthread_的出口呢

这不是必需的。它终止调用线程,使其退出状态可供任何等待的线程使用(即通过pthread_join()。当任何线程从其启动例程返回时,都会隐式调用pthread_exit()。基本上,pthread_exit()函数提供了一个类似于exit()的接口,但基于每个线程(包括取消清理处理程序)。但要小心从取消清理处理程序或从TSD(线程特定数据区域)中分配的对象的析构函数调用pthread_exit()——这可能会导致不良的副作用

关于构造函数-我可以在生成线程后立即销毁thread attr对象(workerSettings),还是必须在线程的整个生命周期内保持有效

是的,您可以立即销毁它:它不会影响已经创建的线程

关于析构函数:这是正确的方法吗

与ctor相同:您可以使用dtor和关闭/停止例程,也可以在dtor中完成所有操作:取决于您的特定需求(例如对象可重用性等)。只要把它弄好,否则就别扔了

您有经验的眼睛是否看到任何同步问题


我可能建议使用pthread_testcancel(),在线程中引入显式取消点,并在控制线程中发出pthread_cancel()+pthread_join()(应返回pthread_cancelled),以停止子线程,而不是同步变量workerRunning。当然,如果它适用于您的情况。

您应该在
pthread\u cond\u wait
返回后立即检查新数据,如果没有新数据,请再次等待。如果您得到了一个虚假的唤醒(可以将其视为内核意外地将一些沉重的东西从楼梯上摔下来而将您唤醒),那么可能会发生这种情况,最好立即等待,而不是更改
工作等待
然后在再次等待之前解锁并重新锁定互斥锁两次

RAII锁类型将使代码更加干净:

    while(true) {
        while(true) {
            {
                scoped_lock l(&manager->mutex);
                if(/* new data available */)
                {
                    /* copy new data from shared to thread memory */
                }
                else
                    break;
            }

            /* process the data in thread memory */

            scoped_lock l(&manager->mutex);
            /* copy results back to shared memory */
        }

        scoped_lock l(&manager->mutex);
        // check if we should continue running
        if(!manager->workerRunning)
            break;

        // wait for new data to arrive
        manager->workerWaiting = true;
        while (!/* new data available */)
            pthread_cond_wait(&manager->condition, &manager->mutex);
        manager->workerWaiting = false;
    }
按照Oleg的建议使用
pthread\u cancel
,可以进一步简化它

在对代码进行编辑以处理虚假唤醒后,如果使用RAII并对其进行重组,则会变得更简单:

    while(true)
    {
        {
            scoped_lock l(&manager->mutex);
            // wait for new data to arrive
            while(manager->workerRunning && !/* new data available*/)
                pthread_cond_wait(&manager->condition, &manager->mutex);

            // check if we should continue running
            if(!manager->workerRunning)
                break;

            /* copy new data from shared to thread memory */
        }

        /* process the data in thread memory */

        scoped_lock l(&manager->mutex);
        /* copy results back to shared memory */
    }
    return NULL;
如果没有作用域锁定之类的功能,如果
/*将新数据从共享内存复制到线程内存*/
/*处理线程内存中的数据*/
引发异常,会发生什么情况?你永远不会解锁互斥锁

RAII类型可以简单到:

struct scoped_lock {
  explicit scoped_lock(pthrad_mutex_t* m) : mx(m) {
    pthread_mutex_lock(mx);
  }
  ~scoped_lock() { pthread_mutex_unlock(mx); }
private:
  pthread_mutex_t* mx;
  scoped_lock(const scoped_lock&);
  scoped_lock operator=(const scoped_lock&);
};

关于你的第二点,是的,从一个线程返回就足够了。您实际上不需要
pthread\u exit
,除非您需要从调用的函数中退出线程。好的,谢谢。我错过了什么交易吗?我不喜欢pthread_t对象在任何地方都没有被销毁的事实。它应该被
pthread_join
调用“销毁”。忙碌等待永远都是不正常的,而且不清楚它在这段代码中的预期用途。为什么要在调用
pthread\u join
之前让工作线程进入某种特定状态?我希望工作线程挂起在pthread\u cond\u wait调用中,因为否则它可能会错过信号并导致死锁(工作线程挂起在pthread\u cond\u wait中,主线程挂起在pthread\u join中)编辑:我想我可以添加另一个if(workerRunning)在pthread_cond_wait调用修复此问题之前..非常感谢您的回答,我不知道取消线程的可能性。这是比我使用的两个布尔更优雅的解决方案。总之,我在等待信号(互斥被解锁)之前在子线程中执行pthread_testcancel(),然后在dtor中发出pthread_cancel,然后发出一个信号(周围有lock/unlock)如果子线程当前正在等待,然后继续加入子线程;pthread_cancel(tid);pthread_join(tid和status);在子线程中(而不是“检查是否应该继续运行”下的代码):pthread_testc