10

I don't understand why this simple snippet has a dead lock:

#include <atomic>
#include <thread>
#include <memory>

using namespace std;
class Test {
public:

    Test() : mExit( false )
    {
        mThread = thread( bind( &Test::func, this ) );
    }

    ~Test()
    {
        if ( mThread.joinable() )
        {
            mExit = true;
            mThread.join();
        }
    }

private:
    void func() 
    {
        while ( !mExit )
        {
            // do something
        }
    }

private:
    atomic< bool > mExit;
    thread mThread;
};

typedef unique_ptr< Test > TestPtr;
TestPtr gTest;

int main()
{
    gTest = TestPtr( new Test );
    return 0;
}

Edit I typed wrong the contstructor set mExit = true

Edit 2 I'm using msvc2012 with v110_xp toolset.

Edit 3 The issue disappear if I explicity call gTest.release() inside main

Elvis Dukaj
  • 7,142
  • 12
  • 43
  • 85
  • I typed wrong, sorry. The issue remain. – Elvis Dukaj Jun 19 '13 at 13:19
  • Hmm. This probably doesn't get better until you use the atomic properly. And use a debugger to find out what the thread is doing. Considerable odds that it is off in the weeds, blocking on some OS call instead of burning 100% core checking that exit condition over and over again. – Hans Passant Jun 19 '13 at 13:24
  • do you have the same issue if gTest is defined inside main? – Balog Pal Jun 19 '13 at 13:25
  • You shouldn't access `mExit` unguarded among different threads. Use a mutex or another syncronization mechanism to protect access to `mExit`. – πάντα ῥεῖ Jun 19 '13 at 13:27
  • @BalogPal no I've not – Elvis Dukaj Jun 19 '13 at 13:28
  • @g-makulik - `mExit` is atomic, so no additional synchronization is needed. – Pete Becker Jun 19 '13 at 13:29
  • I think that this is a issue with msvc 2012 implementation. Seems the global object aren't destroyed correctly. In effect if I explicit call the destructor of Test the deadlock disappear – Elvis Dukaj Jun 19 '13 at 13:34
  • 1
    I remember seeing a simar issue with global objects and thread lifetimes here just the other day. Of course, now I can't find it. The just was that global object destruction and threads don't play well together and you should ensure all threads are joined when `main()` exits. – Chad Jun 19 '13 at 13:35
  • 1
    @Chad Perhaps you were referring to [this question](http://stackoverflow.com/questions/10915233/stdthreadjoin-hangs-if-called-after-main-exits-when-using-vs2012-rc)? – ComicSansMS Jun 19 '13 at 13:37
  • Is it msvc implementation issue, or the standard say to don't destroy thread outside main func? – Elvis Dukaj Jun 19 '13 at 13:37
  • 1
    @Chad: http://stackoverflow.com/questions/17093164/why-does-this-c-static-singleton-never-stop – Pragmateek Jun 19 '13 at 13:38
  • 1
    @pramateek - yes! This one too http://stackoverflow.com/questions/10915233/stdthreadjoin-hangs-if-called-after-main-exits-when-using-vs2012-rc – Chad Jun 19 '13 at 13:40
  • 2
    @elvis.dukaj It is a [bug in VS](https://connect.microsoft.com/VisualStudio/feedback/details/747145) – ComicSansMS Jun 19 '13 at 13:40
  • another msvc bug... ok thank you – Elvis Dukaj Jun 19 '13 at 13:42

3 Answers3

8

I have just had this issue, so I post the real answer for others.

In visual studio at least, there is an "exit lock", that is locked when a thread enters the exit code (ie. after main() for the main thread, and after f() for std::thread(f)).

As your Test class is only destructed after main() completes, the "exit lock" is locked. Only then you set mExit = true; and the other thread is allowed to complete. This other thread then waits to obtain the "exit lock" which is already taken by the main thread, while the main thread waits in mThread.join(); resulting in the deadlock.

So yes, you need to join all your threads before the main thread has completed.

god
  • 1,668
  • 1
  • 11
  • 7
  • So, what is the solution for this case? How to avoid the deadlock of this "exit lock"? – TonySalimi Feb 27 '19 at 10:42
  • 1
    Don't join threads after `main()` has returned. In particular, if a class joins a thread in its destructor, never make an instance of this class as a global variable. – god Mar 27 '19 at 08:48
3

To me the code looks okay, if its okay with local dut bad with global I'd suspect class related to deinit sequence. The join happens deeply in exit, and implementation might have eliminated some structures already. It should not be the case, but possible.

In any case I always avoid starting thread before main, and leaving any reaching end of main. I consider that only asking for trouble. If you can rearrange it to force join at little erlier point the whole problem might evaporate.

Also you should probably use atomic_flag over atomic.

Balog Pal
  • 16,195
  • 2
  • 23
  • 37
  • Have I some benefits using atomic_flag over atomic or atomic_bool? I've to investigate better atomics. – Elvis Dukaj Jun 19 '13 at 13:41
  • atomic_flag is a sure genuine atomic thing, the most basic element. Other types are allowed to be implemented with internal mutex. IMO unlikely, but why take the chance. – Balog Pal Jun 19 '13 at 13:43
  • @BalogPal Because `atomic_flag` is a royal pain to use? ;) – ComicSansMS Jun 19 '13 at 13:45
  • @ComicSansMS: well, that's indeed a valid motivation. (For such simple cases the difference should not be big.) – Balog Pal Jun 19 '13 at 13:56
0

Changing code from

gTest = TestPtr( new Test );

to

auto gTest = std::make_unique();

eliminate the problem.

the problem as point above with global object.