C#如果volatile不是',则可选择锁定;这不是个好主意

C#如果volatile不是',则可选择锁定;这不是个好主意,c#,multithreading,locking,volatile,sudoku,C#,Multithreading,Locking,Volatile,Sudoku,我很抱歉,我知道这个话题已经做得很死了(我已经读过了,我已经读过了,还有更多),但我有一个问题,我不知道如何“正确地”做 目前,我的多线程数独策略代码如下: public class MultithreadedStrategy : ISudokuSolverStrategy { private Sudoku Sudoku; private List<Thread> ThreadList = new List<Thread>(); private O

我很抱歉,我知道这个话题已经做得很死了(我已经读过了,我已经读过了,还有更多),但我有一个问题,我不知道如何“正确地”做

目前,我的多线程数独策略代码如下:

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;
                }
            }
        }
    }
}
公共类多线程策略:ISudokuSolverStrategy
{
私人数独;
私有列表ThreadList=新列表();
私有对象solvedLocker=新对象();
解决了私人问题;
公共问题解决了//这太慢了!
{
得到
{
锁(解算锁)
{
返回-已解决;
}
}
设置
{
锁(解算锁)
{
_已解决=值;
}
}
}
私有int线程;
私有ConcurrentQueue=新ConcurrentQueue();
公共多线程策略(int t)
{
螺纹=t;
已解决=错误;
}
公共数独求解(数独)
{
//在我看来似乎可以想象,可能没有
//只有一个选项的起点。
//因此,我们可能需要搜索多棵树。
WriteLine(“警告:这可能需要大量内存。”);
数独=数独;
//在队列上抛出节点
int firstPos=Sudoku.FindZero();
foreach(数独中的inti.AvailableNumbers(firstPos))
{
数独。值[firstPos]=i;
排队(新节点(firstPos,i,false,数独));
}
//设置线程
对于(int i=0;i
(是的,我做过带递归和不带递归的DFS,并使用BFS,这是上述策略修改的内容)

我想知道是否允许我更改我的
私人文件
私有易失性解决并清除访问器。我认为这可能是一件坏事,因为我的
ProcessQueue()
方法更改了
\u已解决的状态
我是否正确?我知道布尔函数是原子函数,但我不想让编译器优化打乱读/写语句的顺序(特别是因为写只发生一次)


基本上,lock语句会给该策略的运行时间增加数十秒。如果没有锁,它的运行速度要快得多(尽管与DFS相比相对较慢,因为
currentNode.GenerateChildrenAndRecordSudoku()中的内存分配)

volatile
用于防止优化,例如缓存和重新排序单个变量的读/写操作。在这种情况下使用它正是它的设计目的。我不知道您担心什么


lock
是一个缓慢但有效的替代方案,因为它隐式地引入了内存围栏,但在您的情况下,您使用
锁只是为了内存围栏的副作用,这并不是一个好主意。

因此,首先,修复while循环以仅连接线程

    //Set them running
    foreach (Thread t in ThreadList)
        t.Start();

    //Wait until solution found (conditional timeout?)
    foreach (Thread t in ThreadList)
        t.Join(/* timeout optional here */);
然后是什么时候关闭线程的问题。我的建议是在类上引入一个等待句柄,然后在工作线程中循环这个句柄

ManualResetEvent mreStop = new ManualResetEvent(false);
//...
while(!mreStop.WaitOne(0))
{
    //...
现在只需修改已解决的属性,以通知所有线程它们应该退出

public bool Solved
{
    get
    {
        return _solved;
    }
}

// As Eric suggests, this should be a private method, not a property set.
private void SetCompleted()
{
    _solved = value;
    mreStop.Set();
}

这种方法的好处是,如果线程未能在超时时间内退出,您可以向mreStop发送信号,让其停止工作进程,而无需将_solved设置为true。

在进入备选方案之前:通过访问布尔volatile,在这里使用低锁解决方案可能是安全的。这种情况非常理想,因为不太可能y表示您有复杂的观察顺序要求。(“volatile”并不保证观察到多个volatile操作具有来自多个线程的一致顺序,只是读和写具有获取和释放语义。)

然而,低锁解决方案让我非常紧张,除非我确定我需要,否则我不会使用

我要做的第一件事是找出锁上有这么多争用的原因。一个未争用的锁应该需要20-80纳秒;只有当锁被争用时,性能才会显著下降。为什么锁被争用得这么厉害?解决这个问题,性能问题就会消失

如果争用无法减少,我可能会做的第二件事是使用读写器锁。如果我正确理解您的场景,您将有多个读写器,而只有一个写写器,这是读写器锁的理想选择

撇开波动性问题不谈:正如其他人所指出的,线程逻辑中有一些基本错误,比如在布尔值上旋转。这类错误很难纠正