Input / ThinkingThreads

Discussion of chess software programming and technical issues.

Moderator: Ras

Richard Allbert
Posts: 795
Joined: Wed Jul 19, 2006 9:58 am

Input / ThinkingThreads

Post by Richard Allbert »

Hi!

Another question about a problem which has me tearing my hair out.

I've been trying to implement two threads, one for input and one for thinking.

The idea is this: The main() function calls process_uci(), in which a function is called init_threads() that starts a thread in a compute loop (all will be shown below)

So, I end up with the main thread getting input, and the compute thread waiting for an event called "thinkEvent" to be signalled starting the search. As soon as the signal comes, th think thread resets the thinkEvent.

Problem is, when I start the search (from main thread SetEvent(thinkEvent) )
and at some point enter "stop", all is well, and the the think thread pauses waiting to be signalled again.

However, if I leave the search to time out from normal tc play, it ignores the waitforevent().

I hope this makes sense...

Here are the functions (excuse the mess)

Code: Select all


/*
called from main(), computing thread started here
*/

void process_uci()
{
    setbuf(stdout, NULL);
    setbuf(stdout, NULL);
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

	string input;

	init_threads(); // --> thread init

	initucioptions();
    sayversion();
	sayucioptions();
	cout<<"uciok"<<endl;

	int pos;
	while(getline(cin, input))
	{
	  fflush(stdout);
	  EnterCriticalSection(&critid);
      if(islog()) logger.file<<"\nUCI COMMAND IN : "<<input<<endl;
	  LeaveCriticalSection(&critid);

	 if(findsubstr("uci", input, pos))
	 {
	  sayversion();
	  sayucioptions();
	  cout<<"uciok"<<endl;
	  continue;
	 }
	 else if(findsubstr("isready", input, pos))
	 {
	  cout<<"readyok"<<endl;
	  continue;
	 }
	 else if(findsubstr("quit", input, pos))
	 {
	 	EnterCriticalSection(&critid);
        tree->status->quit_called = true;
        LeaveCriticalSection(&critid);
        //now signal the event so the search thread picks up the quit
        SetEvent(thinkEvent);
	 	break;
	 }
	 else if(findsubstr("ucinewgame", input, pos))
	 {
	 	continue;
	 }
	 else if(findsubstr("position", input, pos))
	 {
	 	uciposition(input);
		continue;
	 }
	 else if(findsubstr("setoption", input, pos))
	 {
                setucioptions(input);
                continue;
	 }
	 else if(findsubstr("go", input, pos))
	 {
         uci_go(input);
		 continue;
	 }
	 else if(findsubstr("stop", input, pos))
	 {
		send_stop();
		continue;
	 }
	 else if(findsubstr("ponderhit", input, pos))
	 {
		EnterCriticalSection(&critid);
        tree->param->ponderhit = true;
	    LeaveCriticalSection(&critid);
		continue;
	 }
	 else if(findsubstr("eval", input, pos))
	 {
		 cout<<"\n eval score: "<<eval();
		 cout<<endl;
		 continue;
	 }
	 else if(findsubstr("print", input, pos))
	 {
		 printboard();
		 cout<<endl;
		 continue;
	 }
	 else
	 {
	  cout<<"unknown command "<<input<<endl;
	  continue;
	 }
	}
	//cout<<"waiting for search thread to stop"<<endl;
    WaitForSingleObject(thredid, INFINITE);
  //  cout<<" done, now quitting"<<endl;
}

/*
please ignore "stopThinkEvent". it's another event used for xboard mode pondering hacking I am trying to do :)
*/ 

void send_stop()
{
    EnterCriticalSection(&critid);
	if(islog())logger.file<<"Input Thread setting stop "<<endl;
    tree->status->interrupted = true;
	LeaveCriticalSection(&critid);

	WaitForSingleObject(stopThinkEvent, INFINITE); // wait for stopThink to be signalled
	ResetEvent(stopThinkEvent);
}


void init_threads()
{
    thinkEvent = CreateEvent(NULL,TRUE,FALSE,TEXT("thinkEvent Created\n"));
    stopThinkEvent = CreateEvent(NULL,TRUE,FALSE,TEXT("stopThinkEvent Created\n"));
    if(thinkEvent==NULL)printf( "thinkEvent() failed.\n" );

    InitializeCriticalSectionAndSpinCount(&critid,0);

    thredid = (HANDLE)_beginthread( thread_compute, 0, (void*)12 );
}

/*
here, last action is to signal thinkEvent
*/

void uci_go(string command)
{
	init_go();

	string input;
	int pos;

	 if(findsubstr("depth", command, pos))
	 {
		  setdepth(strtouint(command, pos+6));
          setmodetpm(false);
          setmodemtg(false);
          setdepthlimit(true);
          #ifdef DEBUG
		  cout<<" set depth to "<<GETDEPTH<<endl;
		  #endif
	 }
	 if(findsubstr("movetime", command, pos))
	 {
		  settimepermove(strtouint(command, pos+9));
          setmodetpm(true);
          setmodemtg(false);
          setdepthlimit(false);
          setdepth(maxply);
	 }
	 if(findsubstr("infinite", command, pos))
	 {
	     setmode(smINFINITE);
	 }
	 if(findsubstr("wtime", command, pos))
	 {
	     setmovetime(strtoint(command, pos+6), cW);
	     setmodetpm(false);
         setdepth(maxply);
	 }
	 if(findsubstr("btime", command, pos))
	 {
	     setmovetime(strtoint(command, pos+6), cB);
	     setmodetpm(false);
         setdepth(maxply);
	 }
	 if(findsubstr("winc", command, pos))
	 {
	     setinc(strtoint(command, pos+5),cW);
	 }
	 if(findsubstr("binc", command, pos))
	 {
	     setinc(strtoint(command, pos+5),cW);
	 }
	 if(findsubstr("ponder", command, pos))
	 {
	     setmode(smPONDER);
	 }
	 if(findsubstr("movestogo", command, pos))
	 {
	    setmovestogo(strtoint(command, pos+10),SIDENOW);
        setmodemps(strtoint(command, pos+10));
	 }

	 if(islog())
     {
	   logger.file <<"Jabba entering think \nmovestogo "<<GETMTG(cW)<<"(w) "<<GETMTG(cB)<<"(b)";
	   logger.file <<"\ndepth "<<GETDEPTH;
	   logger.file <<"\nwtime "<<GETMOVETIME(cW);
	   logger.file <<"\nbtime "<<GETMOVETIME(cB);
	   logger.file <<"\nmovetime "<<GETTPM<<"\n";
     }

      SetEvent(thinkEvent); //kick the think thread into gear
}

/*
here, the idea is that thinkEvent has been signalled in the uci_go(), the think thred finishes waiting, resets the event, and when the loop comes around after thinking, it's waiting again due to the reset. This works if there is a manual intervaention in the think (e.g enter stop or quit) but not if the search times out on normal tc play
*/
void thread_compute(void *) //runs parallel with input thread
{

    //reset quit variable
    EnterCriticalSection(&critid);
    tree->status->quit_called = false;
    LeaveCriticalSection(&critid);

    bool leave = false;
    uint best;

    log_options();
    for(;;) //infinite loop where the search thread resides, until quit has been set to true by the main thread
	 {
           //if(islog())logger.file<<"Think Thread waiting"<<endl;
           cout<<"Think Thread waiting "<<endl;
           WaitForSingleObject(thinkEvent, INFINITE); // start thinking signal
           ResetEvent(thinkEvent);
           //if(islog())logger.file<<"Think Thread go"<<endl;
           cout<<"Think Thread go"<<endl;

           EnterCriticalSection(&critid);
           if(tree->status->quit_called)
           {
               cout<<"Quit seen "<<endl;
               leave = true;
           }
           logboard();
           LeaveCriticalSection(&critid);
           if(leave)
           {
               cout<<"leave is set, breaking "<<endl;
               break;
           }

           startsearchtimer(SIDENOW, tree->status->mode, tree->param->ponderhit);
           resetdata();
           preparerootlist();//also limits depth in the case of one legal move
          // EnterCriticalSection(&critid);
             //  if(islog())logger.file<<"Think Thread going to iterdeep "<<endl;
           cout<<"\nThink Thread going to iterdeep "<<endl;
         //  LeaveCriticalSection(&critid);
           iter_deep(); //iterative deepening loop
           if(!tree->param->tuning) ttablesays_stats();
           if(!tree->param->tuning) mlistsays_stats();
           if(!tree->param->tuning) printstats();

         //  EnterCriticalSection(&critid);
          //     if(islog())logger.file<<"Think Thread back from iterdeep "<<endl;
               cout<<"Think Thread back from iterdeep, saying best "<<endl;
         //  LeaveCriticalSection(&critid);

/*
could be back now due to either a stop intervention or timeout
*/

           say_best();
           if(tree->xb && !tree->status->interrupted && engopt.ponder) // if we've not been interrupted, start pondering in xbmode
           xb_ponder();

           cout<<"\nbottom of loop compute() .. "<<endl;
	     // printboard();
	 }
	 cout<<"\n out of think thread loop ";
}

I'm sorry if that's as clear as mud.....

Thanks for any help.

Richard
User avatar
ilari
Posts: 750
Joined: Mon Mar 27, 2006 7:45 pm
Location: Finland

Re: Input / ThinkingThreads

Post by ilari »

Could it be this:

Code: Select all

if(tree->xb && !tree->status->interrupted && engopt.ponder) // if we've not been interrupted, start pondering in xbmode
    xb_ponder(); 
Maybe pondering (which only happens if the search isn't interrupted) won't let the loop to get back to waiting for a new thinkEvent.
Richard Allbert
Posts: 795
Joined: Wed Jul 19, 2006 9:58 am

Re: Input / ThinkingThreads

Post by Richard Allbert »

Hi,

Thanks for the reply, I should have mentioned, in uci mode tree->xb is false.

But if the general implementation in the loop with

Code: Select all

WaitForSingleObject(thinkEvent, INFINITE); // start thinking signal
           ResetEvent(thinkEvent); 
seems ok to you, then I know I'm looking for something silly, and not incorrect use of the event functions, which was my main concern :)

Thanks

Richard
User avatar
ilari
Posts: 750
Joined: Mon Mar 27, 2006 7:45 pm
Location: Finland

Re: Input / ThinkingThreads

Post by ilari »

Richard Allbert wrote:Hi,

Thanks for the reply, I should have mentioned, in uci mode tree->xb is false.
Ah, of course.
But if the general implementation in the loop with

Code: Select all

WaitForSingleObject(thinkEvent, INFINITE); // start thinking signal
           ResetEvent(thinkEvent); 
seems ok to you, then I know I'm looking for something silly, and not incorrect use of the event functions, which was my main concern :)

Thanks

Richard
It seems okay, though it's been some time since I've played with WINAPI. I wonder why you're not using auto-reset events though. I mean, you're calling ResetEvent after WaitForSingleObject anyway. Not that this will solve your problem...
John Major
Posts: 27
Joined: Fri Dec 11, 2009 10:23 pm

Re: Input / ThinkingThreads

Post by John Major »

You could check the return values of WaitForSingleObject() and ResetEvent().
Sven
Posts: 4052
Joined: Thu May 15, 2008 9:57 pm
Location: Berlin, Germany
Full name: Sven Schüle

Re: Input / ThinkingThreads

Post by Sven »

Could you show the output of your program after termination of iter_deep()?

The expected case seems to be:

Code: Select all

Think Thread back from iterdeep, saying best 
<output of say_best()>
bottom of loop compute() .. 
Think Thread waiting 
<any output from uci thread until it receives "go ..." from user interface>
<any output of uci_go("go ...")>
Think Thread go
Maybe there is some problem with ResetEvent()?

Sven
Richard Allbert
Posts: 795
Joined: Wed Jul 19, 2006 9:58 am

Re: Input / ThinkingThreads

Post by Richard Allbert »

Hi Sven / John,

I'll post when I get home tonight from work - I looked at the return value from resetEvent() last night and it returned 1, afaik, anything but 0 is ok?

I'll post the output later, thanks for the replies

Richard
Richard Allbert
Posts: 795
Joined: Wed Jul 19, 2006 9:58 am

Re: Input / ThinkingThreads

Post by Richard Allbert »

:D

I didn't realíse there was AutoRestEvent until reading more of the MSDN documentation yesterday - I put it in, same problem.

I'm getting that (often occuring) feeling that there is a silly mistake somewhere :)

Richard
Sven
Posts: 4052
Joined: Thu May 15, 2008 9:57 pm
Location: Berlin, Germany
Full name: Sven Schüle

Re: Input / ThinkingThreads

Post by Sven »

Can you reproduce the problem easily in console mode?

The following example which is derived from your code seems to work perfectly, at least under VS2010 Express:

Code: Select all

#include <windows.h>
#include <process.h>
#include <iostream>

using namespace std;

bool quit_called = false;
bool interrupted = false;
CRITICAL_SECTION critid;
HANDLE thinkEvent;
HANDLE threadid;

void terminate(char const * str)
{
    cout << str << " failed (" << GetLastError() << ")" << endl;
    exit(1);
}

void iter_deep()
{
    interrupted = false;
    for (int i = 0; i < 50 && !interrupted && !quit_called; i++)
    {
        cout << "iter_deep() is working [" << i << "]..." << endl;
        Sleep(200);
    }
    if (interrupted)
    {
        cout << "iter_deep() was interrupted" << endl;
    }
    else
    {
        cout << "iter_deep() got timeout" << endl;
    }
}

void thread_compute(void *)
{
    EnterCriticalSection(&critid);
    quit_called = false;
    LeaveCriticalSection(&critid);

    bool leave = false;

    for(;;)
    {
        cout << "Think Thread waiting " << endl;
        if (WaitForSingleObject(thinkEvent, INFINITE) == WAIT_FAILED) terminate("WaitForSingleObject");
        if (!ResetEvent(thinkEvent)) terminate("ResetEvent");
        cout << "Think Thread go" << endl;

        EnterCriticalSection(&critid);
        if (quit_called)
        {
            cout << "Quit seen " << endl;
            leave = true;
        }
        LeaveCriticalSection(&critid);
        if (leave)
        {
            cout << "leave is set, breaking " << endl;
            break;
        }

        cout << endl << "Think Thread going to iterdeep" << endl;
        iter_deep();
        cout << "Think Thread back from iterdeep" << endl;
    }
    cout << endl << "out of think thread loop";
}

void process_uci()
{
    thinkEvent = CreateEvent(NULL,TRUE,FALSE,TEXT("thinkEvent Created\n"));
    if (thinkEvent == NULL) terminate("CreateEvent");

    if (!InitializeCriticalSectionAndSpinCount(&critid,0)) terminate("InitializeCriticalSectionAndSpinCount");

    threadid = (HANDLE)_beginthread( thread_compute, 0, (void*)12 );
    if (threadid == NULL) terminate("_beginthread");

    char input[256];

    while (cin.getline(input, sizeof(input)))
    {
        if (!strcmp(input, "quit"))
        {
            EnterCriticalSection(&critid);
            quit_called = true;
            LeaveCriticalSection(&critid);
            if (!SetEvent(thinkEvent)) terminate("SetEvent");
            break;
        }
        else
        if (!strcmp(input, "go"))
        {
            if (!SetEvent(thinkEvent)) terminate("SetEvent");
            continue;
        }
        else
        if (!strcmp(input, "stop"))
        {
            EnterCriticalSection(&critid);
            interrupted = true;
            LeaveCriticalSection(&critid);
            continue;
        }
        else
        {
            cout << "unknown command " << input << endl;
            continue;
        }
    }
    if (WaitForSingleObject(threadid, INFINITE) == WAIT_FAILED) terminate("WaitForSingleObject");
}

int main(void)
{
    process_uci();
    return 0;
}
Sven
Richard Allbert
Posts: 795
Joined: Wed Jul 19, 2006 9:58 am

Re: Input / ThinkingThreads

Post by Richard Allbert »

That works! :D :D

This is going to read really stupidly, but my code also works (today).

I have no idea why... with exactly the same commands as yesterday.
:? :?:

I need to go now, but I'll be back later to play around with it, and do a bit more MSDN reading.

Thanks a lot for the help

Richard