4

我有以下经理<->工人情况:

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)还是必须在线程的整个生命周期内保持有效?
  • 关于析构函数:这是正确的方法吗?

最重要的是:

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

谢谢你的帮助!

4

2 回答 2

3

你问...

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

在大多数情况下,RAII 足以处理对象创建和资源获取。在某些情况下,您可能希望实现延迟资源初始化:当您第一次构造对象时,然后您继续进行初始化。例如,这可以通过 ctor(默认或参数化)和打开/启动例程来实现。尽管您也可以在 ctor 中执行此操作,并通过在进程堆中分配对象(通过 operator new)来实现延迟对象创建。这取决于您的要求、软件设计注意事项和公司软件开发标准。因此,您可以在 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。当然,如果它适用于您的情况。

于 2012-06-26T10:44:32.260 回答
2

您应该在返回后立即检查新数据pthread_cond_wait,如果没有新数据则再次等待。如果你得到一个虚假的唤醒,就会发生这种情况(把它想象成内核不小心把重物从楼梯上掉下来把你吵醒了),最好立即等待而不是更改,workerWaiting然后解锁和重新锁定互斥锁两次,然后再次等待.

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会进一步简化它。

在您编辑代码以处理虚假唤醒之后,如果您使用 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&);
};
于 2012-06-26T15:43:03.497 回答