2

i'm currently writing a c/c++ dll for later use mostly in Delphi and i'm more familiar with threads in Delphi than c/c++ and especially boost. So i wonder how i can achieve the following scenario?

class CMyClass
{
    private:
        boost::thread* doStuffThread;
    protected:
        void doStuffExecute(void)
        {
            while(!isTerminationSignal()) // loop until termination signal
            {
                // do stuff
            }

            setTerminated(); // thread is finished
        };
    public:
        CMyClass(void)
        {
            // create thread
            this->doStuffThread = new boost::thread(boost::bind(&CMyClass::doStuffExecute, this));
        };

        ~CMyClass(void)
        {
            // finish the thread
            signalThreadTermination();
            waitForThreadFinish();

            delete this->doStuffThread;

            // do other cleanup
        };
}

I have red countless articles about boost threading, signals and mutexes but i don't get it, maybe because it's friday ;) or is it not doable how i think to do it?

Regards Daniel

Daniel Mann
  • 158
  • 1
  • 5

1 Answers1

8

Just use an atomic boolean to tell the thread to stop:

class CMyClass
{
private:
    boost::thread doStuffThread;
    boost::atomic<bool> stop;
protected:
    void doStuffExecute()
    {
        while(!stop) // loop until termination signal
        {
            // do stuff
        }

        // thread is finished
    };
public:
    CMyClass() : stop(false)
    {
        // create thread
        doStuffThread = boost::thread(&CMyClass::doStuffExecute, this);
    };

    ~CMyClass()
    {
        // finish the thread
        stop = true;
        doStuffThread.join();

        // do other cleanup
    };
}

To wait for the thread to finish you just join it, that will block until it is finished and can be joined. You need to join the thread anyway before you can destroy it, or it will terminate your program.

There is no need to use a pointer and create the thread with new, just use a boost::thread object directly. Creating everything on the heap is wasteful, unsafe and poor style.

There is no need to use boost::bind to pass arguments to the thread constructor. For many many years boost::thread has supported passing multiple arguments to its constructor directly and it does the binding internally.

It's important that stop has been initialized to false before the new thread is created, otherwise if the new thread is spawned very quickly it could check the value of stop before it is initialized, and might happen to read a true value from the uninitialized memory, and then it would never enter the loop.

On the subject of style, writing foo(void) is considered by many C++ programmers to be a disgusting abomination. If you want to say your function takes no arguments then just write foo().

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • Does it really need to be atomic here? I don't see where multiple threads would be accessing it. – Brandon Kohn Aug 01 '14 at 14:59
  • 2
    @BrandonKohn, one thread writes a value to the boolean, another thread reads it. That seems like multiple threads accessing it to me. – Jonathan Wakely Aug 01 '14 at 15:00
  • Yes, but what harm is there? It's not a race condition. – Brandon Kohn Aug 01 '14 at 15:03
  • 2
    It the very definition of a race condition! The C++ standard says any data race is undefined behaviour. – Jonathan Wakely Aug 01 '14 at 15:03
  • I think a race condition is when you have multiple threads *writing* to a variable. It's not a big deal. I just think you could save the costs of the atomic synchronization in this case and just use a bool. – Brandon Kohn Aug 01 '14 at 15:05
  • 3
    The standard is very clear: "Two expression evaluations _conflict_ if one of them modifies a memory location (1.7) and the other one accesses or modifies the same memory location. ... The execution of a program contains a _data race_ if it contains two potentially concurrent conflicting actions, at least one of which is not atomic, and neither happens before the other ... Any such data race results in undefined behaviour." Also, without the memory barriers provided by `std::atomic` there is no guarantee that the `stop = true` update will ever be observed by the other thread. – Jonathan Wakely Aug 01 '14 at 15:05
  • See: http://stackoverflow.com/questions/34510/what-is-a-race-condition. And note that a read operation won't stop a write from happening. The thread would just see it on the next iteration. – Brandon Kohn Aug 01 '14 at 15:07
  • 4
    I didn't say it will stop it _happening_ I said it won't necessarily become visible to the other thread in a timely manner, if at all. The "costs of the atomic synchronization" are exactly what is needed to make the code correct. If you're going to decide you don't need to use atomics to avoid data races please never write any multithreaded code for anything that I depend on. – Jonathan Wakely Aug 01 '14 at 15:10
  • It's not that I don't know how to use atomics. It's that I believe in this instance, the worst thing that can happen is that the thread runs additional loops while stop is being set to true. I think it's a valid trade-off to avoid the synchronization cost of the atomic. – Brandon Kohn Aug 01 '14 at 15:16
  • 8
    What part of **UNDEFINED BEHAVIOUR** doesn't concern you? Without the atomics the compiler is allowed to transform the loop into: `if (!stop) while (true) { /* do work */ }` because it knows that a non-atomic, non-volatile variable being read cannot change except in a program with undefined behaviour, so it can assume it never changes – Jonathan Wakely Aug 01 '14 at 15:20
  • I don't think there is undefined behavior. The thread in the loop either will eventually see stop == true. How could it not? I don't see how the compiler could transform `while(!stop) into if(!stop) while(1)...` – Brandon Kohn Aug 01 '14 at 15:21
  • 7
    See my previous comment, or read [Benign data races: what could possibly go wrong?](https://software.intel.com/en-us/blogs/2013/01/06/benign-data-races-what-could-possibly-go-wrong). Just because you don't understand the problem doesn't mean it isn't real. – Jonathan Wakely Aug 01 '14 at 15:23
  • 7
    Also section 2.3 in [How to miscompile programs with “benign” data races](https://www.usenix.org/legacy/event/hotpar11/tech/final_files/Boehm.pdf) is *exactly* this situation. Those two links are written by some of the world's top experts on data races and multithreading, but hey, maybe they're just paranoid and you're correct. – Jonathan Wakely Aug 01 '14 at 15:28
  • I think you're taking this discussion a bit personally. If you cannot debate something without responding as though attacked, it makes it hard to learn. BTW, I find the blog interesting and convincing, but I wonder what happens if the bool were marked volatile. – Brandon Kohn Aug 01 '14 at 15:37
  • 5
    `volatile` is neither necessary nor sufficient for interthread communication in C++. http://www.drdobbs.com/parallel/volatile-vs-volatile/212701484 – Jonathan Wakely Aug 01 '14 at 15:45
  • 10
    @BrandonKohn multithreading is to singlethreading as relativity theory is to classical mechanics. **Your intuitions about simultaneity and ordering of events don't carry over**. Furthermore, the Q&A you quoted about race conditions is not C++ specific. For C++, the Standard has a very precise statement about it, and that's the only relevant quote here. – TemplateRex Aug 02 '14 at 21:56
  • @TemplateRex: Quantum physics, with stuff like entanglement, would be better, I feel. – Puppy Aug 02 '14 at 22:00
  • 3
    @Puppy Quantum phyiscs is more like UB, or heisenbugs. Look at it, and it's gone. Multithreading really is like relativity because it messes with your intuition about relative ordering of events. – TemplateRex Aug 02 '14 at 22:05
  • 2
    @BrandonKohn "I don't think there is undefined behavior." [It is, **in the context of C++**](http://stackoverflow.com/questions/25082563/25082904#comment39026862_25082904). Or maybe you don't know [the implications of having UB in the program.](http://stackoverflow.com/questions/24296571) "I wonder what happens if the bool were marked volatile." - [It is *not* enough](http://stackoverflow.com/questions/2484980/why-is-volatile-not-considered-useful-in-multithreaded-c-or-c-programming). Also, there is nothing emotional in showing actual arguments. – milleniumbug Aug 02 '14 at 22:18
  • I believe I understand the issue better now. Thanks for all the helpful comments. – Brandon Kohn Aug 02 '14 at 22:44
  • I would like to point out that there is a lot of conflicting information from people who are well qualified. [link](http://msdn.microsoft.com/en-us/magazine/cc546569.aspx). "Sometimes races may be safe [snip] [e.g.] there could be a global flag called done, for which there is only one writer but many readers. The writer thread sets the flag to tell all the threads to terminate safely. All reader threads may be running in a loop using while (!done), repeatedly reading the flag. Once a thread notices that the done flag is set, it will exit its while loop. In most cases, this is a benign race." – Brandon Kohn Aug 02 '14 at 22:45
  • 3
    @BrandonKohn that link predates the c++11 standard that defined multithreading in c++ *for the first time*. – TemplateRex Aug 03 '14 at 05:07
  • Threading has been a reality in C++ for well over 20 years. The newer standards will certainly steer things, but I think most compiler vendors will want to maintain a high degree of backward compatibility to avoid breaking legacy code. BTW, I agree that it's UB now and you should use atomics, but that doesn't mean it won't work in practice. I'd like to see an example of a compiler that would not do the right thing in this case. – Brandon Kohn Aug 03 '14 at 12:24
  • 2
    @BrandonKohn: Well, what you "think" and what is actually true are two very different things. Your predefined notion of "the right thing" is equally flawed, and it shows that you haven't bothered to read the material presented to you. Did you know that Jonathan Wakely is among the world's top C++ experts, develops GCC's C++ standard library implementation, and sits on the ISO C++ committee? Yeah. That's who you're arguing with. But, hey, you know best... – Lightness Races in Orbit Aug 03 '14 at 13:53
  • When did I say a blog was a peer-reviewed published scholarly article? – Brandon Kohn Aug 03 '14 at 14:12
  • 1
    @BrandonKohn there is no backward compatibility to consider for compilers because there was no *standardized* threading before c++11. One of the nasty things of UB is that things *may* continue to run as you expect. Until you upgrade the compiler, or use a higher optimization level, switch OS etc... No diuagnoistics required :-) – TemplateRex Aug 03 '14 at 19:27