PThread初学者 - 启动,同步,停止工作线程

时间:2012-06-26 08:10:26

标签: 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 = (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是否在其构造函数中生成一个新线程被认为是一种不好的做法? (我只会有一个Manager对象,所以我猜应该没问题)
  • pthread_exit怎么样 - 我在很多教程中都看到了这个但是我不明白为什么它应该存在?我不能简单地返回退出线程的函数吗?我也认为返回NULL是死代码,但是当它丢失时gcc会发出警告,因为它显然无法知道pthread_exit已经在那时杀死了线程。
  • 关于构造函数 - 我可以在产生线程后立即销毁线程attr对象(workerSettings),还是必须在线程的整个生命周期内保持有效?
  • 关于析构函数:这是正确的方法吗?

最重要的是:

  • 您经验丰富的眼睛是否看到任何同步问题?

感谢您的帮助!

2 个答案:

答案 0 :(得分:3)

你问......

  

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

在大多数情况下,RAII足以接近对象创建和资源获取。在某些情况下,您可能希望实现延迟资源初始化:首次构造对象时,稍后继续初始化。例如,这可以通过ctor(默认或参数化)和打开/启动例程来实现。虽然您也可以在ctor中执行此操作,并通过在进程堆中分配对象(通过operator new)来实现deffered对象的创建。这取决于您的要求,软件设计考虑因素和企业软件开发标准。 因此,您可以在ctor中创建一个线程,或者可能希望或需要在应用程序/对象生命周期的后期阶段生成它。

  

pthread_exit

怎么样?

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

  

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

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

  

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

与ctor相同:您可以使用dtor和关闭/停止例程,或者可以在dtor中完成所有操作:取决于您的特定需求(例如,对象可重用性等)。只是让dtor不要扔掉。

  

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

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

答案 1 :(得分:2)

一旦pthread_cond_wait返回,您应该检查新数据,如果没有新数据,请再次等待。如果你得到一个虚假的唤醒,可能会发生这种情况(想想它是因为内核意外地通过在楼梯上放下一些东西而唤醒你),最好立即等待而不是改变workerWaiting然后解锁和重新锁定mutex再次等待两次。

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

使用pthread_cancel作为Oleg建议会进一步简化它。

在您编辑代码以处理虚假唤醒之后,如果您使用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;

如果不使用scoped_lock,如果/* copy new data from shared to thread memory *//* process the data in thread memory */抛出异常会发生什么?你永远不会解锁互斥锁。

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