简体   繁体   English

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

[英]PThread beginner - starting, syncing, stopping a worker thread

I have the following manager<->worker situation: 我有以下经理< - >工人情况:

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

I'm not completely sure about this at several places: 我在几个地方对此并不完全确定:

  • Is the fact that Manager spawns a new thread in its constructor considered a bad practice? 事实上,Manager在其构造函数中生成一个新线程被认为是一种不好的做法吗? (I will only have one Manager object, so i guess that should be fine) (我只会有一个Manager对象,所以我觉得应该没问题)
  • What about the pthread_exit - i see this in many tutorials but i don't quite get why it should be there? 那个pthread_exit - 我在很多教程中都看到了这个但是我不明白为什么它应该在那里? Can't i simply return the function to exit the thread? 我不能简单地返回退出线程的函数吗? I also think the return NULL is dead code, but gcc warns when it's missing because it obviously can't know that pthread_exit already killed the thread at that point. 我也认为返回NULL是死代码,但是当它丢失时gcc会发出警告,因为它显然无法知道pthread_exit已经在那时杀死了线程。
  • About the constructor - can i destroy the thread attr object (workerSettings) immediately after spawning the thread or does it have to stay valid for the entire lifetime of the thread? 关于构造函数 - 我可以在产生线程后立即销毁线程attr对象(workerSettings),还是必须在线程的整个生命周期内保持有效?
  • About the destructor: Is this the right way to do this? 关于析构函数:这是正确的方法吗?

And most importantly: 最重要的是:

  • Do your experienced eyes see any synchronization issues there? 您经验丰富的眼睛是否看到任何同步问题?

Thanks for your help! 谢谢你的帮助!

You ask... 你问...

Is the fact that Manager spawns a new thread in its constructor considered a bad practice? 事实上,Manager在其构造函数中生成一个新线程被认为是一种不好的做法吗?

In most cases, RAII is good enough to approach the object creation and resource acquisition. 在大多数情况下,RAII足以接近对象创建和资源获取。 In some cases you may want to achieve the deferred resource initialization: when you first construct an object and later you proceed with the initialization. 在某些情况下,您可能希望实现延迟资源初始化:首次构造对象时,稍后继续初始化。 This can be achieved, for example, via a ctor (either default or parameterized) and open/start routines. 例如,这可以通过ctor(默认或参数化)和打开/启动例程来实现。 Though you may also do it in the ctor and achieve the deffered object creation by allocating the object in the process heap (via operator new). 虽然您也可以在ctor中执行此操作,并通过在进程堆中分配对象(通过operator new)来实现deffered对象的创建。 It depends on your requirements, software design considerations and corporate software development standards. 这取决于您的要求,软件设计考虑因素和企业软件开发标准。 So, you may create a thread in ctor, or may want or need to spawn it in the later stage of the application/object lifecycle. 因此,您可以在ctor中创建一个线程,或者可能希望或需要在应用程序/对象生命周期的后期阶段生成它。

What about the pthread_exit pthread_exit怎么样?

It is not required. 这不是必需的。 It terminates the calling thread, making its exit status available to any waiting threads (ie via pthread_join()). 它终止调用线程,使其退出状态可用于任何等待的线程(即通过pthread_join())。 An implicit call to pthread_exit() occurs when any thread returns from its start routine. 当任何线程从其启动例程返回时,会发生对pthread_exit()的隐式调用。 Basically, the pthread_exit() function provides an interface similar to exit() but on a per-thread basis (including cancelation cleanup handlers). 基本上,pthread_exit()函数提供类似于exit()的接口,但是基于每个线程(包括取消清理处理程序)。 But beware of calling pthread_exit() from cancelation cleanup handlers or from destructors of objects allocated in the TSD (thread-specific data area) - it can lead to undesirable side effects. 但要注意从取消清除处理程序或在TSD(特定于线程的数据区域)中分配的对象的析构函数调用pthread_exit() - 它可能导致不良副作用。

About the constructor - can i destroy the thread attr object (workerSettings) immediately after spawning the thread or does it have to stay valid for the entire lifetime of the thread? 关于构造函数 - 我可以在产生线程后立即销毁线程attr对象(workerSettings),还是必须在线程的整个生命周期内保持有效?

Yes, you can destroy it right away: it will not affect already created threads. 是的,你可以立即销毁它:它不会影响已经创建的线程。

About the destructor: Is this the right way to do this? 关于析构函数:这是正确的方法吗?

Same thing as for ctor: you may use dtor and close/stop routine or can do it all in the dtor: depends on your specific needs (eg object reusability etc). 与ctor相同:您可以使用dtor和关闭/停止例程,或者可以在dtor中完成所有操作:取决于您的特定需求(例如,对象可重用性等)。 Just make the dtor not throw. 只是让dtor不要扔。

Do your experienced eyes see any synchronization issues there? 您经验丰富的眼睛是否看到任何同步问题?

I may suggest using pthread_testcancel(), to introduce the explicit cancelation point in a thread, and issue pthread_cancel() + pthread_join() (should return PTHREAD_CANCELED) in the control thread to stop the child thread, instead of synch variable workerRunning. 我可能建议使用pthread_testcancel(),在线程中引入显式取消点,并在控制线程中发出pthread_cancel()+ pthread_join()(应返回PTHREAD_CANCELED)以停止子线程,而不是同步变量workerRunning。 Of course, if it is applicable in your case. 当然,如果适用于您的情况。

You should check for new data as soon as pthread_cond_wait returns, and wait again if there's no new data. 您应该在pthread_cond_wait返回后立即检查新数据,如果没有新数据则再次等待。 That can happen if you get a spurious wake (think of it as the kernel accidentally waking you up by dropping something heavy down the stairs), and it would be better to wait immediately instead of changing workerWaiting then unlocking and relocking the mutex twice before waiting again. 如果你得到一个虚假的唤醒,可能会发生这种情况(想想它是因为内核意外地通过在楼梯上放下一些东西而唤醒你),最好立即等待而不是更换workerWaiting等待然后在等待之前解锁和重新锁定互斥锁两次再次。

An RAII lock type would make the code so much cleaner: 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;
    }

Using pthread_cancel as Oleg suggests would simplify it even further. 使用pthread_cancel作为Oleg建议将进一步简化它。

Following your edit to the code to handle spurious wake-ups, it becomes much simpler if you use RAII and restructure it: 在编辑代码以处理虚假唤醒之后,如果您使用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;

Without something like scoped_lock, what happens if /* copy new data from shared to thread memory */ or /* process the data in thread memory */ throws an exception? 如果没有像scoped_lock这样的东西,如果/* copy new data from shared to thread memory *//* process the data in thread memory */会引发异常,会发生什么? You'll never unlock the mutex. 你永远不会解锁互斥锁。

The RAII type could be as simple as: 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&);
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM