0

同期化されたマルチスレッドと非同期化されたマルチスレッドの違いを示すタスクがあります。そのため、クライアントの銀行口座からお金を引き出すことをシミュレートするアプリケーションを作成しました。いくつかのスレッドのそれぞれがランダムなユーザーを選択し、アカウントからお金を引き出します。すべてのスレッドは、すべてのアカウントを一度撤回する必要があります。最初はスレッドが同期されますが、2 回目はそうではありません。したがって、同期されたスレッドと同期されていないスレッドによって引き出されたアカウントには違いがあるはずです。また、ユーザー数とスレッド数が異なれば、違いも異なるはずです。しかし、私のアプリケーションでは、1000 スレッドだけ違いがあります。したがって、非同期スレッドの結果が同期スレッドの結果と大きく異なる必要があります。クラス ユーザー:

public class User : IComparable
{
    public string Name { get; set; }

    public int Start { get; set; }

    public int FinishSync { get; set; }

    public int FinishUnsync { get; set; }

    public int Hypothetic { get; set; }

    public int Differrence { get; set; }
...
}

出金方法:

public void Withdraw(ref List<User> users, int sum, bool isSync)
    {
        int ind = 0;
        Thread.Sleep(_due);
        var rnd = new Random(DateTime.Now.Millisecond);
        //used is list of users,  withrawed by the thread
        while (_used.Count < users.Count)
        {
            while (_used.Contains(ind = rnd.Next(0, users.Count))) ; //choosing a random user
            if (isSync) //isSync = if threads syncroized
            {
                if (Monitor.TryEnter(users[ind]))
                {
                    try
                    {
                        users[ind].FinishSync = users[ind].FinishSync - sum;
                    }

                    finally
                    {
                        Monitor.Exit(users[ind]);
                    }
                }

            }
            else
            {
                lock (users[ind])
                {
                    users[ind].FinishUnsync = users[ind].FinishUnsync - sum;
                }
            }
            _used.Add(ind);
        }
        done = true;
    }

スレッドは次のように作成されます。

 private void Withdrawing(bool IsSync)
    {
        if (IsSync)
        {
            for (int i = 0; i < _num; i++)
            {
                _withdrawers.Add(new Withdrawer(Users.Count, _due, _pause));
                _threads.Add(new Thread(delegate()
 { _withdrawers[i].Withdraw(ref Users, _sum, true); }));
                _threads[i].Name = i.ToString();
                _threads[i].Start();
                _threads[i].Join();
            }
        }
        else
        {
            for (int i = 0; i < _num; ++i)
            {
                _withdrawers.Add(new Withdrawer(Users.Count, _due, _pause));
                _threads.Add(new Thread(delegate()
 { _withdrawers[i].Withdraw(ref Users, _sum, false); }));
                _threads[i].Name = i.ToString();
                _threads[i].Start();
            }
        }
    }

Withdraw クラスをこのように変更しました。問題は、デリゲートとは別にスレッドを作成することにあった可能性があります。

class Withdrawer
{
    private List<int>[] _used;
    private int _due;
    private int _pause;
    public int done;
    private List<Thread> _threads;
    public Withdrawer(List<User> users, int n, int due, int pause, int sum)
    {
        _due = due;
        _pause = pause;
        done = 0;
        _threads = new List<Thread>(users.Count);
        InitializeUsed(users, n);
        CreateThreads(users, n, sum, false);
        _threads.Clear();
        while (done < n) ;
        Array.Clear(_used,0,n-1);
        InitializeUsed(users, n);
        CreateThreads(users, n, sum, true);
    }

    private void InitializeUsed(List<User> users, int n)
    {
        _used = new List<int>[n];
        for (int i = 0; i < n; i++)
        {
            _used[i] = new List<int>(users.Count);
            for (int j = 0; j < users.Count; j++)
            {
                _used[i].Add(j);
            }
        }
    }

    private void CreateThreads(List<User> users, int n, int sum, bool isSync)
    {
        for (int i = 0; i < n; i++)
        {
            _threads.Add(new Thread(delegate() { Withdraw(users, sum, isSync); }));
            _threads[i].Name = i.ToString();
            _threads[i].Start();
        }
    }

    public void Withdraw(List<User> users, int sum, bool isSync)
    {
        int ind = 0;
        var rnd = new Random();
        while (_used[int.Parse(Thread.CurrentThread.Name)].Count > 0)
        {
            int x = rnd.Next(_used[int.Parse(Thread.CurrentThread.Name)].Count);
            ind = _used[int.Parse(Thread.CurrentThread.Name)][x];
            if (isSync)
            {
                lock (users[ind])
                {
                    Thread.Sleep(_due);
                    users[ind].FinishSync -= sum;
                }
            }
            else
            {
                Thread.Sleep(_due);
                users[ind].FinishUnsync -= sum;
            }
            _used[int.Parse(Thread.CurrentThread.Name)][x] = _used[int.Parse(Thread.CurrentThread.Name)][_used[int.Parse(Thread.CurrentThread.Name)].Count - 1];
            _used[int.Parse(Thread.CurrentThread.Name)].RemoveAt(_used[int.Parse(Thread.CurrentThread.Name)].Count - 1);
            Thread.Sleep(_pause);
        }
        done++;
    }
}

問題は、FinishUnSync の値が正しく、FinishSync の値がまったく正しくないことです。Thread.Sleep(_due); および Thread.Sleep(_pause);

リソースを「保持」するために使用されます.bc私のタスクは、スレッドがリソースを取得し、_due msの間それを保持し、処理後、_pause msを待ってから終了することです.

4

1 に答える 1

5

あなたのコードは何も役に立たず、同期アクセスと非同期アクセスの違いを示していません。対処しなければならないことがたくさんあります。

_usedコード内のコメントは、それがスレッドによってアクセスされたユーザーのリストであると言います。どうやらスレッドごとに作成しているようです。それが本当なら、どうすればいいのかわかりません。_used見た目から、すべてのスレッドにアクセスできると思います。そのリストのスレッドごとのバージョンを作成している場所はどこにもありません。また、命名規則は、それがクラス スコープであることを示しています。

そのリストがスレッドごとではない場合、データが常に同じである理由を説明するのに大いに役立ちます。複数のスレッドからリストを更新しているため、ここでも実際の競合状態があります。

それが_used本当にスレッドごとのデータ構造であると仮定します。. .

あなたはこのコードを持っています:

        if (isSync) //isSync = if threads syncroized
        {
            if (Monitor.TryEnter(users[ind]))
            {
                try
                {
                    users[ind].FinishSync = users[ind].FinishSync - sum;
                }

                finally
                {
                    Monitor.Exit(users[ind]);
                }
            }

        }
        else
        {
            lock (users[ind])
            {
                users[ind].FinishUnsync = users[ind].FinishUnsync - sum;
            }
        }

これらは両方とも同期を提供します。このisSync場合、スレッドがすでにユーザーをロックしている場合、2 番目のスレッドはその更新に失敗します。2 番目のケースでは、2 番目のスレッドは最初のスレッドが終了するのを待ってから、更新を行います。どちらの場合でも、Monitorまたはを使用するとlock、同時更新が防止されます。

isSyncそれでも、複数のスレッドが同時にコードを実行できる場合は、違いが見られる可能性があります。ただし、同期されたケースでは複数のスレッドを実行させないため、違いはわかりません。つまり、次のものがあります。

    if (IsSync)
    {
        for (int i = 0; i < _num; i++)
        {
            _withdrawers.Add(new Withdrawer(Users.Count, _due, _pause));
            _threads.Add(new Thread(delegate()
               { _withdrawers[i].Withdraw(ref Users, _sum, true); }));
            _threads[i].Name = i.ToString();
            _threads[i].Start();
            _threads[i].Join();
        }
    }
    else
    {
        for (int i = 0; i < _num; ++i)
        {
            _withdrawers.Add(new Withdrawer(Users.Count, _due, _pause));
            _threads.Add(new Thread(delegate()
                { _withdrawers[i].Withdraw(ref Users, _sum, false); }));
            _threads[i].Name = i.ToString();
            _threads[i].Start();
        }
    }

このIsSync場合、スレッドを開始し、それが完了するのを待ってから別のスレッドを開始します。あなたのコードはマルチスレッドではありません。また、「非同期」の場合は、lock同時更新を防ぐために を使用しています。そのため、一度に 1 つのスレッドのみを実行して同時更新を防止する場合もあれば、lock. 違いはありません。

他に注目すべきことは、ユーザーをランダムに選択する方法が非常に非効率的であり、発生している問題の一部である可能性があることです。基本的にあなたがしていることは、乱数を選び、それがリストにあるかどうかを確認することです. そうであれば、もう一度やり直してください。リストは増え続けます。簡単な実験では、すべての乱数を取得する前に、0 から 1,000 の間で 7,000 個の乱数を生成する必要があることがわかりました。そのため、スレッドは次の未使用のアカウントを見つけるために膨大な時間を費やしています。つまり、同じユーザー アカウントを同時に処理する可能性が低くなります。

3 つのことを行う必要があります。まず、Withdrawlメソッドを次のように変更します。

        if (isSync) //isSync = if threads syncroized
        {
            // synchronized. prevent concurrent updates.
            lock (users[ind])
            {
                users[ind].FinishSync = users[ind].FinishSync - sum;
            }
        }
        else
        {
            // unsynchronized. It's a free-for-all.
            users[ind].FinishUnsync = users[ind].FinishUnsync - sum;
        }

が真Withdrawingであるかどうかに関係なく、メソッドは同じである必要がありIsSyncます。つまり、次のようにする必要があります。

        for (int i = 0; i < _num; ++i)
        {
            _withdrawers.Add(new Withdrawer(Users.Count, _due, _pause));
            _threads.Add(new Thread(delegate()
               { _withdrawers[i].Withdraw(ref Users, _sum, false); }));
            _threads[i].Name = i.ToString();
            _threads[i].Start();
        }

これで、常に複数のスレッドが実行されます。唯一の違いは、ユーザー アカウントへのアクセスが同期されているかどうかです。

最後に、_usedリストをリストへのインデックスのリストにしますusers。何かのようなもの:

_used = new List<int>(users.Count);
for (int i = 0; i < _used.Count; ++i)
{
    _used[i] = i;
}

ここで、ユーザーを選択するときは、次のようにします。

var x = rnd.Next(_used.Count);
ind = _used[x];
// now remove the item from _used
_used[x] = _used[_used.Count-1];
_used.RemoveAt(_used.Count-1);

そうすれば、すべてのユーザーをより効率的に生成できます。n ユーザーを生成するには、n 個の乱数が必要です。

ちょっとしたコツ:

Thread.Sleepメソッドで呼び出しを行う理由がわかりませんWithdraw。どのようなメリットがあると思いますか?

コンストラクターDateTime.Now.Millisecondに渡す本当の理由はありません。Random呼び出すだけでシードnew Random()に使用されます。Environment.TickCountシードを 0 ~ 1,000 の数値に制限したい場合を除きます。

于 2013-10-24T16:07:54.703 に答える