I'm sorry I know this topic has been done to death (I've read I've read this and this and a few more) but there is one issue I have which I am not sure how to do 'correctly'.
Currently my code for a multithreaded Sudoku strategy is the following:
public class MultithreadedStrategy : ISudokuSolverStrategy
{
private Sudoku Sudoku;
private List<Thread> ThreadList = new List<Thread>();
private Object solvedLocker = new Object();
private bool _solved;
public bool Solved // This is slow!
{
get
{
lock (solvedLocker)
{
return _solved;
}
}
set
{
lock (solvedLocker)
{
_solved = value;
}
}
}
private int threads;
private ConcurrentQueue<Node> queue = new ConcurrentQueue<Node>();
public MultithreadedStrategy(int t)
{
threads = t;
Solved = false;
}
public Sudoku Solve(Sudoku sudoku)
{
// It seems concevable to me that there may not be
// a starting point where there is only one option.
// Therefore we may need to search multiple trees.
Console.WriteLine("WARNING: This may require a large amount of memory.");
Sudoku = sudoku;
//Throw nodes on queue
int firstPos = Sudoku.FindZero();
foreach (int i in Sudoku.AvailableNumbers(firstPos))
{
Sudoku.Values[firstPos] = i;
queue.Enqueue(new Node(firstPos, i, false, Sudoku));
}
//Setup threads
for (int i = 0; i < threads; i++)
{
ThreadList.Add(new Thread(new ThreadStart(ProcessQueue)));
ThreadList[i].Name = String.Format("Thread {0}", i + 1);
}
//Set them running
foreach (Thread t in ThreadList)
t.Start();
//Wait until solution found (conditional timeout?)
foreach (Thread t in ThreadList)
t.Join();
//Return Sudoku
return Sudoku;
}
public void ProcessQueue()
{
Console.WriteLine("{0} running...",Thread.CurrentThread.Name);
Node currentNode;
while (!Solved) // ACCESSING Solved IS SLOW FIX ME!
{
if (queue.TryDequeue(out currentNode))
{
currentNode.GenerateChildrenAndRecordSudoku();
foreach (Node child in currentNode.Children)
{
queue.Enqueue(child);
}
// Only 1 thread will have the solution (no?)
// so no need to be careful about locking
if (currentNode.CurrentSudoku.Complete())
{
Sudoku = currentNode.CurrentSudoku;
Solved = true;
}
}
}
}
}
(Yes I have done DFS with and without recursion and using a BFS which is what the above strategy modifies)
I was wondering whether I am allowed to change my private bool _solved;
to a private volatile solved;
and get rid of the accessors. I think this might be a bad thing because my ProcessQueue()
method changes the state of _solved
Am I correct? I know booleans are atomic but I don't want compiler optimisations to mess up the order of my read/write statements (esp. since the write only happens once).
Basically the lock statement adds tens of seconds to the run time of this strategy. Without the lock it runs an awful lot faster (although is relatively slow compared to a DFS because of the memory allocation within currentNode.GenerateChildrenAndRecordSudoku()