3

申し訳ありませんが、このトピックが完全に終わったことを知っています (私はこれを読んだ、これこれを読んだ、その他いくつかを読んだ) が、「正しく」行う方法がわからない問題が 1 つあります。

現在、マルチスレッド数独戦略のコードは次のとおりです。

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;
                }
            }
        }
    }
}

(はい、再帰の有無にかかわらず、上記の戦略が変更するBFSを使用してDFSを実行しました)

private bool _solved;myを aに変更してprivate volatile solved;、アクセサーを削除できるかどうか疑問に思っていました。私のメソッドはAm I correct?ProcessQueue()の状態を変更するため、これは悪いことかもしれません。_solvedブール値がアトミックであることは知っていますが、コンパイラの最適化によって読み取り/書き込みステートメントの順序が乱れることは望ましくありません (特に、書き込みは 1 回しか行われないため)。

基本的に、lock ステートメントにより、この戦略の実行時間が数十秒長くなります。ロックがなければ、非常に高速に実行されます (ただし、DFS 内のメモリ割り当てのため、DFS に比べて比較的低速です)。currentNode.GenerateChildrenAndRecordSudoku()

4

3 に答える 3

11

代替案に入る前に: ブール値の volatile へのアクセスを作成することで、ここではローロック ソリューションを使用するのがおそらく安全です。複雑な観測順序の要件がある可能性は低いため、この状況は理想的です。(「volatile」は、複数の volatile 操作が複数のスレッドから一貫した順序であることが観察されることを保証するものではなく、読み取りと書き込みに取得と解放のセマンティクスがあることのみを保証します。)

ただし、ローロック ソリューションは非常に神経質になり、必要であると確信しない限り使用しません。

私が最初に行うことは、ロックでこれほど多くの競合が発生する理由を突き止めることです。非競合ロックには 20 ~ 80 ナノ秒かかります。ロックが競合している場合にのみ、パフォーマンスが大幅に低下するはずです。ロックの競合が激しいのはなぜですか? その問題を修正すると、パフォーマンスの問題が解消されます。

競合を減らすことができない場合に 2 番目に行うことは、リーダー/ライター ロックを使用することです。私があなたのシナリオを正しく理解していれば、多くのリーダーと 1 つのライターだけを持つことができます。これは、リーダー/ライター ロックに理想的です。

ボラティリティの問題はさておき: 他の人が指摘しているように、スレッド化ロジックには、ブール値のスピンなどの基本的な間違いがあります。このようなものを正しくするのは難しいです。ここでは、独自のスレッド ロジックをローリングするよりも高レベルの抽象化として、Task Parallel Library を使用することを検討してください。TPL は、複数のスレッドで重要な作業を行う必要がある問題に最適です。(TPL は魔法のように非スレッド セーフ コードをスレッド セーフにするわけではないことに注意してください。ただし、スレッドではなくタスクを処理するように、より高いレベルの抽象化を提供します。TPL にスレッドのスケジュールを設定させてください。)

最後に、数独ソルバーに数十秒かかるという考えは、率直に言って、ソルバーがあまり良くないことを示しています。数独問題は、理論的に考えられる最悪のケースでは、スレッド数に関係なく、すぐに解決するのが難しい問題です。しかし、「新聞」品質の数独の場合は、数分の 1 秒で実行されるソルバーを作成できるはずです。すべてを数百ミリ秒で実行できる場合は、複数のスレッドにワークアウトする必要はありません。

興味があれば、数独問題の解決策をすばやく見つける C# プログラムがあります。

http://blogs.msdn.com/b/ericlippert/archive/tags/graph+colouring/

于 2012-01-05T21:29:28.500 に答える
3

最初に、スレッドに参加するように 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))
{
    //...

ここで、Solved プロパティを変更して、終了する必要があるすべてのスレッドにシグナルを送るだけです...

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();
}

このアプローチの利点は、スレッドがタイムアウト期間内に終了に失敗した場合、_solved を true に設定せずに mreStop にシグナルを送信してワーカーを停止できることです。

于 2012-01-05T21:23:39.280 に答える
1

volatileIS は、単一変数の読み取り/書き込みのキャッシングや並べ替えなどの最適化を防ぐために使用されます。この場合に使用することは、まさにそのために設計されたものです。あなたの懸念が何であるかわかりません。

lockメモリフェンスを暗黙的に導入するため、遅いが機能する代替手段ですが、あなたの場合lock、メモリフェンスの副作用のためだけに使用していますが、これは本当に良い考えではありません.

于 2012-01-05T21:05:19.517 に答える