【问题标题】:PThread beginner - starting, syncing, stopping a worker threadPThread 初学者 - 启动、同步、停止工作线程
【发布时间】:2012-06-26 08:10:26
【问题描述】:

我有以下经理工人情况:

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 已经在那个时候杀死了线程。
  • 关于构造函数 - 我可以在生成线程后立即销毁线程属性对象(workerSettings)还是必须在线程的整个生命周期内保持有效?
  • 关于析构函数:这是正确的方法吗?

最重要的是:

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

感谢您的帮助!

【问题讨论】:

  • 关于你的第二点,是的,从一个线程返回就足够了。你真的不需要pthread_exit,除非你需要例如从被调用函数中退出线程。
  • 好的,谢谢。我缺少任何释放吗?我不喜欢 pthread_t 对象在任何地方都没有被破坏的事实。
  • 它应该被pthread_join调用“销毁”。
  • 忙等待永远不行,而且不清楚这段代码的预期目的是什么。为什么你希望工人在调用pthread_join 之前进入某种特定状态?
  • 我希望工作线程挂在 pthread_cond_wait 调用中,否则它可能会错过信号并导致死锁(工作线程挂在 pthread_cond_wait 中,主线程挂在 pthread_join 中)编辑:我想我可以添加在 pthread_cond_wait 调用之前的另一个 if(workerRunning) 来解决这个问题..

标签: c++ synchronization pthreads backgroundworker


【解决方案1】:

你问...

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

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

pthread_exit 呢

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

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

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

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

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

您有经验的眼睛是否发现那里有任何同步问题?

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

【讨论】:

  • 非常感谢您的回答,我不知道取消线程的可能性。这比我使用的两个布尔值要优雅得多。总结一下 - 我在等待信号之前在子线程中执行 pthread_testcancel()(互斥锁被解锁),在 dtor 中我发出 pthread_cancel,然后我发出一个信号(在它周围带有锁定/解锁)如果子线程当前正在等待,然后继续加入子线程。
  • 在你做的父线程中:pthread_create(&tid); pthread_cancel(tid); pthread_join(tid, &status);在您执行的子线程中(而不是“检查我们是否应该继续运行”下的代码): pthread_testcancel();无需使用互斥锁。 pthread_testcancel() 测试控制线程是否已发送取消信号,如果收到则不返回。在子线程使用 pthread_testcancel 对其进行检查之前,取消将处于挂起状态。您可能希望使用 pthread_cleanup_push 安装清理例程:不会执行 pthread_testcancel 之后的代码。线程已终止。
  • pthread_cond_wait() 是取消点,因此不需要明确的ptthread_testcancel
  • 不幸的是,我发现 androids 仿生 libc 不支持取消 api,所以我坚持使用 bool 东西.. 但我更新了代码以处理虚假信号并删除了现在未使用的 workerWaiting flag,现在看起来干净多了。
【解决方案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;
    }

按照 Oleg 的建议使用 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&);
};

【讨论】:

  • 嗯,这看起来确实是一个有趣的概念。我无法使用 boost,但无论如何实现这样的作用域锁应该不会太难。
  • 我并不是在建议使用 boost,它的 scoped_lock 无论如何都适用于 boost::mutex,而不是 pthread_mutex_tRAII 对于简单、正确的 C++ 代码至关重要。
  • 哦,好吧,我只是在谷歌上搜索 scoped_lock :) 我会阅读那个 wiki 页面。
  • 参见上面的简化示例
  • 哇,你的简化真的很简洁,我也喜欢 RAII 的概念,它开辟了一个全新的可能性范围!非常感谢您的帮助!
猜你喜欢
  • 1970-01-01
  • 2021-01-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多