Simple thread solution to an common problem
Posted: 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.
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.
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.
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;
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();
}
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.