mirror of
https://github.com/HChaZZY/Stockfish.git
synced 2025-12-25 03:26:24 +08:00
RootInTB was an incorrectly shared global, probably leading to wrong scoreing Minor: setting TB global state from input by all threads (all threads write same values) setting Limits global state by all threads (idem) thread counting for finalization CI can be enabled once races are fixed in the learner, manually goes like: ``` make clean && make -j2 ARCH=x86-64-modern sanitize=thread optimize=no debug=yes build ../tests/instrumented_learn.sh --sanitizer-thread ``` Needs some review.
153 lines
4.6 KiB
C++
153 lines
4.6 KiB
C++
#ifndef _MULTI_THINK_
|
|
#define _MULTI_THINK_
|
|
|
|
#include "learn.h"
|
|
|
|
#include "misc.h"
|
|
#include "thread_win32_osx.h"
|
|
|
|
#include <atomic>
|
|
#include <limits>
|
|
#include <functional>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <cstdint>
|
|
|
|
|
|
// Learning from a game record, when making yourself think and generating a fixed track, etc.
|
|
// Helper class used when multiple threads want to call Search::think() individually.
|
|
// Derive and use this class.
|
|
struct MultiThink
|
|
{
|
|
static constexpr std::uint64_t LOOP_COUNT_FINISHED = std::numeric_limits<std::uint64_t>::max();
|
|
|
|
MultiThink() : prng{}, loop_count(0) { }
|
|
|
|
MultiThink(std::uint64_t seed) : prng(seed), loop_count(0) { }
|
|
|
|
MultiThink(const std::string& seed) : prng(seed), loop_count(0) { }
|
|
|
|
// Call this function from the master thread, each thread will think,
|
|
// Return control when the thought ending condition is satisfied.
|
|
// Do something else.
|
|
// ・It is safe for each thread to call Learner::search(),qsearch()
|
|
// Separates the substitution table for each thread. (It will be restored after the end.)
|
|
// ・Book is not thread safe when in on the fly mode, so temporarily change this mode.
|
|
// Turn it off.
|
|
// [Requirements]
|
|
// 1) Override thread_worker()
|
|
// 2) Set the loop count with set_loop_max()
|
|
// 3) set a function to be called back periodically (if necessary)
|
|
// callback_func and callback_interval
|
|
void go_think();
|
|
|
|
// If there is something you want to initialize on the derived class side, override this,
|
|
// Called when initialization is completed with go_think().
|
|
// It is better to read the fixed trace at that timing.
|
|
virtual void init() {}
|
|
|
|
// A thread worker that is called by creating a thread when you go_think()
|
|
// Override and use this.
|
|
virtual void thread_worker(size_t thread_id) = 0;
|
|
|
|
// Called back every callback_seconds [seconds] when go_think().
|
|
std::function<void()> callback_func;
|
|
uint64_t callback_seconds = 600;
|
|
|
|
// Set the number of times worker processes (calls Search::think()).
|
|
void set_loop_max(uint64_t loop_max_) { loop_max = loop_max_; }
|
|
|
|
// Get the value set by set_loop_max().
|
|
uint64_t get_loop_max() const { return loop_max; }
|
|
|
|
// [ASYNC] Take the value of the loop counter and add the loop counter after taking it out.
|
|
// If the loop counter has reached loop_max, return UINT64_MAX.
|
|
// If you want to generate a phase, you must call this function at the time of generating the phase,
|
|
// Please note that the number of generated phases and the value of the counter will not match.
|
|
uint64_t get_next_loop_count() {
|
|
std::unique_lock<std::mutex> lk(loop_mutex);
|
|
if (loop_count >= loop_max)
|
|
return LOOP_COUNT_FINISHED;
|
|
return loop_count++;
|
|
}
|
|
|
|
// [ASYNC] For returning the processed number. Each time it is called, it returns a counter that is incremented.
|
|
uint64_t get_done_count() {
|
|
std::unique_lock<std::mutex> lk(loop_mutex);
|
|
return ++done_count;
|
|
}
|
|
|
|
// Mutex when worker thread accesses I/O
|
|
std::mutex io_mutex;
|
|
|
|
protected:
|
|
// Random number generator body
|
|
AsyncPRNG prng;
|
|
|
|
private:
|
|
// number of times worker processes (calls Search::think())
|
|
std::atomic<uint64_t> loop_max;
|
|
// number of times the worker has processed (calls Search::think())
|
|
std::atomic<uint64_t> loop_count;
|
|
// To return the number of times it has been processed.
|
|
std::atomic<uint64_t> done_count;
|
|
|
|
// Mutex when changing the variables in ↑
|
|
std::mutex loop_mutex;
|
|
|
|
// Thread end flag.
|
|
std::atomic<uint64_t> threads_finished;
|
|
};
|
|
|
|
// Mechanism to process task during idle time.
|
|
// master passes the task with push_task_async() whenever you like.
|
|
// When slave executes on_idle() in its spare time, it retrieves one task and continues execution until there is no queue.
|
|
// Convenient to use when you want to write MultiThink thread worker in master-slave method.
|
|
struct TaskDispatcher
|
|
{
|
|
typedef std::function<void(size_t /* thread_id */)> Task;
|
|
|
|
// slave calls this function during idle.
|
|
void on_idle(size_t thread_id)
|
|
{
|
|
Task task;
|
|
while ((task = get_task_async()) != nullptr)
|
|
task(thread_id);
|
|
|
|
sleep(1);
|
|
}
|
|
|
|
// Stack [ASYNC] task.
|
|
void push_task_async(Task task)
|
|
{
|
|
std::unique_lock<std::mutex> lk(task_mutex);
|
|
tasks.push_back(task);
|
|
}
|
|
|
|
// Allocate size array elements for task in advance.
|
|
void task_reserve(size_t size)
|
|
{
|
|
tasks.reserve(size);
|
|
}
|
|
|
|
protected:
|
|
// set of tasks
|
|
std::vector<Task> tasks;
|
|
|
|
// Take out one [ASYNC] task. Called from on_idle().
|
|
Task get_task_async()
|
|
{
|
|
std::unique_lock<std::mutex> lk(task_mutex);
|
|
if (tasks.size() == 0)
|
|
return nullptr;
|
|
Task task = *tasks.rbegin();
|
|
tasks.pop_back();
|
|
return task;
|
|
}
|
|
|
|
// a mutex for accessing tasks
|
|
std::mutex task_mutex;
|
|
};
|
|
|
|
#endif
|