/// Thread::idle_loop() is where the thread is parked when it has no work to do. /// The parameter 'master_sp', if non-NULL, is a pointer to an active SplitPoint /// object for which the thread is the master. internal void idle_loop(SplitPoint sp_master, ManualResetEvent initEvent) { if (initEvent != null) { // Signal done initEvent.Set(); } bool use_sleeping_threads = Threads.useSleepingThreads; // If this thread is the master of a split point and all slaves have // finished their work at this split point, return from the idle loop. while ((sp_master == null) || (sp_master.slavesMask != 0)) { // If we are not searching, wait for a condition to be signaled // instead of wasting CPU time polling for work. while (do_sleep || do_exit || (!is_searching && use_sleeping_threads)) { if (do_exit) { Debug.Assert(sp_master == null); return; } // Grab the lock to avoid races with Thread::wake_up() ThreadHelper.lock_grab(sleepLock); // If we are master and all slaves have finished don't go to sleep if ((sp_master != null) && (sp_master.slavesMask == 0)) { ThreadHelper.lock_release(sleepLock); break; } // Do sleep after retesting sleep conditions under lock protection, in // particular we need to avoid a deadlock in case a master thread has, // in the meanwhile, allocated us and sent the wake_up() call before we // had the chance to grab the lock. if (do_sleep || !is_searching) { ThreadHelper.cond_wait(sleepCond, sleepLock); } ThreadHelper.lock_release(sleepLock); } // If this thread has been assigned work, launch a search if (is_searching) { Debug.Assert(!do_sleep && !do_exit); ThreadHelper.lock_grab(Threads.splitLock); Debug.Assert(is_searching); SplitPoint sp = curSplitPoint; ThreadHelper.lock_release(Threads.splitLock); LoopStack ls = LoopStackBroker.GetObject(); Stack[] ss = ls.ss; int ssPos = 0; Position pos = PositionBroker.GetObject(); pos.copy(sp.pos, this); Array.Copy(sp.ss, sp.ssPos - 1, ss, ssPos, 4); ss[ssPos + 1].sp = sp; ThreadHelper.lock_grab(sp.Lock); if (sp.nodeType == NodeTypeC.Root) { Search.search(NodeTypeC.SplitPointRoot, pos, ss, ssPos + 1, sp.alpha, sp.beta, sp.depth); } else if (sp.nodeType == NodeTypeC.PV) { Search.search(NodeTypeC.SplitPointPV, pos, ss, ssPos + 1, sp.alpha, sp.beta, sp.depth); } else if (sp.nodeType == NodeTypeC.NonPV) { Search.search(NodeTypeC.SplitPointNonPV, pos, ss, ssPos + 1, sp.alpha, sp.beta, sp.depth); } else { Debug.Assert(false); } Debug.Assert(is_searching); is_searching = false; #if ACTIVE_REPARENT sp.allSlavesRunning = false; #endif sp.slavesMask &= ~(1UL << idx); sp.nodes += pos.nodes; // Wake up master thread so to allow it to return from the idle loop in // case we are the last slave of the split point. if (use_sleeping_threads && this != sp.master && !sp.master.is_searching) { sp.master.wake_up(); } // After releasing the lock we cannot access anymore any SplitPoint // related data in a safe way becuase it could have been released under // our feet by the sp master. Also accessing other Thread objects is // unsafe because if we are exiting there is a chance are already freed. ThreadHelper.lock_release(sp.Lock); #if ACTIVE_REPARENT // Try to reparent to the first split point, with still all slaves // running, where we are available as a possible slave. for (int i = 0; i < Threads.size(); i++) { Thread th = Threads.threads[i]; int spCnt = th.splitPointsCnt; SplitPoint latest = th.splitPoints[spCnt != 0 ? spCnt - 1 : 0]; if (this.is_available_to(th) && spCnt > 0 && !th.cutoff_occurred() && latest.allSlavesRunning && Utils.more_than_one(latest.slavesMask)) { ThreadHelper.lock_grab(latest.Lock); ThreadHelper.lock_grab(Threads.splitLock); // Retest all under lock protection, we are in the middle // of a race storm here ! if (this.is_available_to(th) && spCnt == th.splitPointsCnt && !th.cutoff_occurred() && latest.allSlavesRunning && Utils.more_than_one(latest.slavesMask)) { latest.slavesMask |= 1UL << idx; curSplitPoint = latest; is_searching = true; } ThreadHelper.lock_release(Threads.splitLock); ThreadHelper.lock_release(latest.Lock); break; // Exit anyhow, only one try (enough in 99% of cases) } } #endif pos.startState = null; pos.st = null; PositionBroker.Free(); LoopStackBroker.Free(ls); } } }
// split() does the actual work of distributing the work at a node between // several available threads. If it does not succeed in splitting the node // (because no idle threads are available, or because we have no unused split // point objects), the function immediately returns. If splitting is possible, a // SplitPoint object is initialized with all the data that must be copied to the // helper threads and then helper threads are told that they have been assigned // work. This will cause them to instantly leave their idle loops and call // search(). When all threads have returned from search() then split() returns. internal static Value split(bool Fake, Position pos, Stack[] ss, int ssPos, Value alpha, Value beta, Value bestValue, ref Move bestMove, Depth depth, Move threatMove, int moveCount, MovePicker mp, int nodeType) { Debug.Assert(pos.pos_is_ok()); Debug.Assert(bestValue > -ValueC.VALUE_INFINITE); Debug.Assert(bestValue <= alpha); Debug.Assert(alpha < beta); Debug.Assert(beta <= ValueC.VALUE_INFINITE); Debug.Assert(depth > DepthC.DEPTH_ZERO); Thread master = pos.this_thread(); if (master.splitPointsCnt >= Constants.MAX_SPLITPOINTS_PER_THREAD) { return(bestValue); } // Pick the next available split point from the split point stack SplitPoint sp = master.splitPoints[master.splitPointsCnt]; sp.parent = master.curSplitPoint; sp.master = master; sp.cutoff = false; sp.slavesMask = 1UL << master.idx; #if ACTIVE_REPARENT sp.allSlavesRunning = true; #endif sp.depth = depth; sp.bestMove = bestMove; sp.threatMove = threatMove; sp.alpha = alpha; sp.beta = beta; sp.nodeType = nodeType; sp.bestValue = bestValue; sp.mp = mp; sp.moveCount = moveCount; sp.pos = pos; sp.nodes = 0; sp.ss = ss; sp.ssPos = ssPos; Debug.Assert(master.is_searching); master.curSplitPoint = sp; int slavesCnt = 0; ThreadHelper.lock_grab(sp.Lock); ThreadHelper.lock_grab(splitLock); for (int i = 0; i < size() && !Fake; ++i) { if (threads[i].is_available_to(master)) { sp.slavesMask |= 1UL << i; threads[i].curSplitPoint = sp; threads[i].is_searching = true; // Slave leaves idle_loop() if (useSleepingThreads) { threads[i].wake_up(); } if (++slavesCnt + 1 >= maxThreadsPerSplitPoint) // Master is always included { break; } } } master.splitPointsCnt++; ThreadHelper.lock_release(splitLock); ThreadHelper.lock_release(sp.Lock); // Everything is set up. The master thread enters the idle loop, from which // it will instantly launch a search, because its is_searching flag is set. // We pass the split point as a parameter to the idle loop, which means that // the thread will return from the idle loop when all slaves have finished // their work at this split point. if (slavesCnt != 0 || Fake) { master.idle_loop(sp, null); // In helpful master concept a master can help only a sub-tree of its split // point, and because here is all finished is not possible master is booked. Debug.Assert(!master.is_searching); } // We have returned from the idle loop, which means that all threads are // finished. Note that setting is_searching and decreasing activeSplitPoints is // done under lock protection to avoid a race with Thread::is_available_to(). ThreadHelper.lock_grab(sp.Lock); // To protect sp->nodes ThreadHelper.lock_grab(splitLock); master.is_searching = true; master.splitPointsCnt--; master.curSplitPoint = sp.parent; pos.nodes += sp.nodes; bestMove = sp.bestMove; ThreadHelper.lock_release(splitLock); ThreadHelper.lock_release(sp.Lock); return(sp.bestValue); }
internal void wake_up() { ThreadHelper.lock_grab(sleepLock); ThreadHelper.cond_signal(sleepCond); ThreadHelper.lock_release(sleepLock); }