C++ Question

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

mcostalba
Posts: 2684
Joined: Sat Jun 14, 2008 9:17 pm

Re: C++ Question

Post by mcostalba »

kinderchocolate wrote:
So we're in the constructor and the start_routine might or might not be called before the constructor ends. We have a problem if it's called before the constructor returns because the derived version is not "ready".

Is this a bug?
No.

There is nothing that prevents you to call a virtual function from a c'tor

http://www.stroustrup.com/bs_faq2.html#vcall

The only side effect is that it will not call the function of the class _derived_ from the one it is building, but will call the function defined in the same class, i.e. it is like the virtual call mechanism is disabled and the virtual function will behave like a normal method.

See also answer 19: http://stackoverflow.com/questions/9621 ... nstructors

"In C++ the Base class will build its version of the virtual method table prior to entering its own construction. At this point a call to the virtual method will end up calling the Base version of the method"
petero2
Posts: 685
Joined: Mon Apr 19, 2010 7:07 pm
Location: Sweden
Full name: Peter Osterlund

Re: C++ Question

Post by petero2 »

mcostalba wrote:
kinderchocolate wrote:
So we're in the constructor and the start_routine might or might not be called before the constructor ends. We have a problem if it's called before the constructor returns because the derived version is not "ready".

Is this a bug?
No.

There is nothing that prevents you to call a virtual function from a c'tor

http://www.stroustrup.com/bs_faq2.html#vcall

The only side effect is that it will not call the function of the class _derived_ from the one it is building, but will call the function defined in the same class, i.e. it is like the virtual call mechanism is disabled and the virtual function will behave like a normal method.
But this is really bad because it means that the MainThread and TimerThread objects can use the base class idle_loop function instead of the derived versions.

Here is a patch to make the bug easier to trigger:

Code: Select all

diff --git a/src/thread.cpp b/src/thread.cpp
index 2b09a69..d27a4bb 100644
--- a/src/thread.cpp
+++ b/src/thread.cpp
@@ -56,6 +56,7 @@ Thread::Thread() /* : splitPoints() */ { // Value-initialization bug in MSVC
       std&#58;&#58;cerr << "Failed to create thread number " << idx << std&#58;&#58;endl;
       &#58;&#58;exit&#40;EXIT_FAILURE&#41;;
   &#125;
+  sleep&#40;1&#41;;
 &#125;
 
Since I upgraded DroidFish to use stockfish 3, I have had several reports from users that stockfish just hangs. Up till now I have not been able to reproduced the problem, but it seems likely that this bug is responsible.
User avatar
sje
Posts: 4675
Joined: Mon Mar 13, 2006 7:43 pm

Some less-related comments

Post by sje »

Having now taken a look at the Stockfish source. I have a few more comments:

1. Why is there a bunch of chess search stuff in Thread which should be in a derived class/struct instead? Currently, it looks like the single TimerThread instance gets all the chess definitions variables and uses none of them. Wouldn't it be better to have a Thread base class which had no chess stuff and have it and its derived classes include only the methods and instance variables which they actually used?

2. Using a pure virtual method (thus defining an interface) for idle_loop() allows the compiler to force compliance and leaves no room for confusion as to which version of idle_loop() is called by whom.

3. I see that in the space of a dozen or so lines, the exit identifier is used for a flag, for a method, and finally for the standard Unix program termination routine. Surely a better naming strategy could be used here.
mar
Posts: 2554
Joined: Fri Nov 26, 2010 2:00 pm
Location: Czech Republic
Full name: Martin Sedlak

Re: Threads in C++: c'tor/d'tor duties

Post by mar »

mcostalba wrote:Regrading starting the thread in c'tor, it is what std::thread does:

http://en.cppreference.com/w/cpp/thread/thread/thread

So we cannot assume it is the wrong thing just because we learnt to do it in another way.
But you are not supposed to derive from std::thread (technically you can - which is why i still consider it flawed). That's a big difference.

Let's say I derive MyThread from Thread which physically starts it. How do you know that MyThread ctor finishes initializion of MyThread before the new physical thread starts to run, potentially starting to use member variable of MyThread which hasn't been potentially constructed yet?
The same applies to virtual destructor. ~Thread calls join() but the physical thread may still be running, accessing MyThread member variables that have been already destroyed.
mcostalba
Posts: 2684
Joined: Sat Jun 14, 2008 9:17 pm

Re: C++ Question

Post by mcostalba »

petero2 wrote: But this is really bad because it means that the MainThread and TimerThread objects can use the base class idle_loop function instead of the derived versions.
OK, now I see it.

Yes this is a bug !

I have tried to quick write a patch with start() and finish(), but this adds some ugly code...I will try also the approach to wrap the idle_loop() in a separate class and derive from it and use composition to add that class to Thread, this should allow to preserve the c'tor and d'tor semantic but guarantee object are fully initialized when called.
Sven
Posts: 4052
Joined: Thu May 15, 2008 9:57 pm
Location: Berlin, Germany
Full name: Sven Schüle

Re: Also, dynamic re-use

Post by Sven »

sje wrote:First, for stylistic correctness, a line should be added at the end of the Finish() routine:

Code: Select all

void Thread&#58;&#58;Finish&#40;void&#41;
&#123;
  if &#40;pthread_join&#40;*&#40;pthread_t *) threadptr, 0&#41;)
    Die&#40;"Thread&#58;&#58;Finish", "pthread_join");
  delete &#40;pthread_t *) threadptr;
  threadptr = 0;  // To be consistent
&#125;
Yet even in the unamended version, the Thread object can have multiple re-uses of sequential Start()/Finish() pairs of calls. This could be handy if the ctor/dtor does a lot of work that doesn't need repeated for sequential activations.

The isrunning instance variable is there for the outside world to see if the thread is still active. In the case of a Task instance, a true value means that the instance is still interested in processing incoming events on it's associated event list.

Generally, the isrunning variable is set to false by the Task instance when the task wants to say good-bye. For an instance of the interactive command processor task, the variable will be set false by an incoming EOF on the command input or by processing an exit command typed by the user. I'll probably add certain incoming signal kinds (i.e., SIG_HUP) as a third way of indicating a farewell request.

Over the years I've seen some really bad abuse of C++. I recall from long ago reading some C++ code which included the line:

Code: Select all

delete this;
Which to this day gives me the shivers considering how it could introduce all kinds of hard-to-debug randomness. Also, what if the instance was allocated on the stack or statically instead of on the heap? All because the guy who wrote it was too lazy to do it right in the first place.
For the reason you mentioned in your last paragraph, the Thread() constructor should initialize threadptr to 0, Thread::Start() should check whether threadptr is really 0 (otherwise Start() was called twice in a row), and Thread::Finish() should check whether threadptr is != 0. Of course you can't ASSERT() these conditions since Start() and Finish() can be called anywhere outside. Simplest would be to do nothing if the == 0 resp. != 0 conditions are not met.

Sven
Aleks Peshkov
Posts: 892
Joined: Sun Nov 19, 2006 9:16 pm
Location: Russia

Re: Also, dynamic re-use

Post by Aleks Peshkov »

Code: Select all

#include <thread>
#include <mutex>
#include <condition_variable>

class ThreadControl &#123;
    std&#58;&#58;mutex sync_guard;
    std&#58;&#58;condition_variable sync;

    enum Status &#123; Ready, Stop, Run &#125;;
    volatile Status status;

    void setStatus&#40;Status To&#41; &#123;
        std&#58;&#58;unique_lock<std&#58;&#58;mutex> lock&#123;sync_guard&#125;;
        if &#40;status != To&#41; &#123;
            status = To;
            sync.notify_all&#40;);
        &#125;
    &#125;

    void signal&#40;Status To&#41; &#123;
        if &#40;status != To&#41; &#123; setStatus&#40;To&#41;; &#125;
    &#125;

    void signal&#40;Status From, Status To&#41; &#123;
        if &#40;status == From&#41; &#123; setStatus&#40;To&#41;; &#125;
    &#125;

    template <Status To> void wait&#40;) &#123;
        std&#58;&#58;unique_lock<std&#58;&#58;mutex> lock&#123;sync_guard&#125;;
        sync.wait&#40;lock, &#91;this&#93;() &#123; return status == To; &#125;);
    &#125;

protected&#58;
    virtual void operator&#40;) () = 0;

public&#58;
    ThreadControl () &#58; status&#123;Ready&#125; &#123;
        auto body = &#91;this&#93;()
        &#123;
            for (;;) &#123;
                this->signal&#40;Ready&#41;;
                this->wait<Run>();
                this->operator&#40;)();
            &#125;
        &#125;;
        std&#58;&#58;thread&#40;body&#41;.detach&#40;);
    &#125;

    bool isReady&#40;) const &#123; return status == Ready; &#125;
    bool isStopped&#40;) const &#123; return status == Stop; &#125;

    void commandRun&#40;) &#123; signal&#40;Ready, Run&#41;; &#125;
    void commandStop&#40;) &#123; signal&#40;Run, Stop&#41;; &#125;

    void waitReady&#40;) &#123; wait<Ready>(); &#125;
&#125;;
mcostalba
Posts: 2684
Joined: Sat Jun 14, 2008 9:17 pm

Re: C++ Question

Post by mcostalba »

mcostalba wrote: Yes this is a bug !
I have hopefully fixed the race. The approach taken is to add a simple helper called new_thread() that mimics operator new, but first creates the object then starts it:

Code: Select all

 template<typename T> T* new_thread&#40;) &#123;
   T* th = new T&#40;);
   thread_create&#40;th->handle, start_routine, th&#41;; // Will go to sleep
   return th;
 &#125;
This allows to minimize code changes and, more importantly, to preserve the "initialization in c'tor" approach (called RAII by C++ people).

Details are here:
https://github.com/mcostalba/Stockfish/ ... d29efe4ee4


I have also reworked thread hierarchy following Steven's suggestion:
https://github.com/mcostalba/Stockfish/ ... 3e7b8ad1fc



Thanks to everybody for your help !
Rein Halbersma
Posts: 741
Joined: Tue May 22, 2007 11:13 am

Re: C++ Question

Post by Rein Halbersma »

mcostalba wrote:
mcostalba wrote: Yes this is a bug !
I have hopefully fixed the race. The approach taken is to add a simple helper called new_thread() that mimics operator new, but first creates the object then starts it:

Code: Select all

 template<typename T> T* new_thread&#40;) &#123;
   T* th = new T&#40;);
   thread_create&#40;th->handle, start_routine, th&#41;; // Will go to sleep
   return th;
 &#125;
This allows to minimize code changes and, more importantly, to preserve the "initialization in c'tor" approach (called RAII by C++ people).
Fail. Epic fail. :shock:

This has nothing to do with RAII. Using create_thread() and delete_thread() is completely unsafe. Whenever you have an uncaught exception anywhere, the thread's destructor will not be called.

Proper RAII is to wrap the thread in a class (preferably a std::unique_ptr), where the constructor creates and the destructor cleans up. This way, whenever an exception happens after the thread has been created, the destructor is guaranteed to be called.
kinderchocolate
Posts: 454
Joined: Mon Nov 01, 2010 6:55 am
Full name: Ted Wong

Re: C++ Question

Post by kinderchocolate »

Yes. Memory leak happens if somehow exception is thrown after the new operator. But it doesn't really matter anyway because there's no point to continue executing if we can't start an engine thread.