Simple thread solution to an common problem

Code, algorithms, languages, construction...
Post Reply
thevinenator
Posts: 68
Joined: Tue Jun 02, 2015 11:02 pm
Real Name: Vince

Simple thread solution to an common problem

Post by thevinenator » Mon Nov 28, 2016 5:18 am

As I learn more about how chess engines are put together, I noticed that most engine coders use the same methods to handling communications from within the search structure; they use callbacks or hooks into the pipes to look for new input to process. This seemed overly complex and the occasional calls to check for input are time consuming.

In my day job , I solved a communication problem using a different method which I now use in my chess engine. I would like to share it here. I'll present my chess engine solution to keep things in context.

Basically, the idea is to use a second thread for the search portion of the engine and use the main program thread for the communications. It is very simple to set up. My engine only uses a single thread for searching, but i'm sure a multi-threaded search solution would still work in this model.

After doing some initialization the following few lines of code are called from the main() function.

Code: Select all

	// start the search thread
	HANDLE hSearch;
	hSearch = (HANDLE)_beginthread(SearchThread, 0, NULL);

	CommLoop();

	return 0;

the _beginthread() call sets up a new thread that is controlled by a routine named SearchThread(). The main() thread then calls CommLoop(), which is described below.

Code: Select all

void SearchThread(void *)
	{
	// this method is started by a thread
	// it stays in this loop until the program exits
	for (;;)
		{
		if (cs.Exit)
			break;

		if (cs.task == TASK_NOTHING) { Sleep(100); }
		else	{
			SearchRun();
			cs.task = TASK_NOTHING;
			}
		}
	_endthread();
	}
This code is basically a state engine with only two states. When the engine is not searching, it will stay in this infinite loop until cs.Exit flag is set (via the communication code), or the search is started by a change to the cs.task variable (also from the communication code). the "cs" variable is a global structure which is shared by all threads. Sleep(100) is called when the engine is not searching in order to keep this thread from using up all the CPU. (100) is a tenth of a second and the time it takes for the tight loop to run once every tenth of second isn't a burden on the computer at all. Anyways, it is only running when the engine is not doing anything.

The CommLoop() handles the data from the input stream and the implementation isn't important to this exercise, but I will say that the code spends most of its time waiting for input and then only reacts once a command is received.

Inside the search code, there still is a call to determine if the search should terminate, but there is no need to check for input.

To exit the program, the communication code sets cs.Exit to true when a "quit" command is received from the UCI overlord or, also from my own console interface (used for testing). The search thread then destroys itself via the _endthread() call. At the same time, the CommLoop() code exits and the main() program exits.

I hope this is useful.
"An Engine's strength flows from the Search. But beware, pruning, extensions, reductions; the dark side of the Search are they. Once you start down the dark path, it will dominate and consume you, as it has to so many developers before.” -- Yoda

H.G.Muller
Posts: 190
Joined: Sun Jul 14, 2013 10:00 am
Real Name: H.G. Muller

Re: Simple thread solution to an common problem

Post by H.G.Muller » Mon Nov 28, 2016 11:13 am

Many engines use a separate thread for input, and if you are using multi-threading in the search I guess it is the logical thing to do. For simple engines it is a bit of a hassle to get involved with multi-threading just for this purpose. (It needs extra libraries, which are not platform independent.) Synchronizing the threads might also be a bit tricky (e.g. knowing when to respond to 'ping').

In XBoard protocol engines can receive a signal (SIGINT) when they get input while they might be searching. This offers a third method for handling input, next to periodic polling and a dedicated input thread. The engine can catch the SIGINT, and then safely read input through a blocking read call, because it is guaranteed to be present. Basically this would put the code you would put in the dedicated input thread in the SIGINT handling routine. Unfortunally this does not work on Windows, because inter-process signals are not supported there.

Post Reply