0

我有一个任务来展示同步和非同步多线程之间的区别。因此,我编写了一个模拟从客户银行账户取款的应用程序。一些线程中的每一个都会选择一个随机用户并从帐户中提取资金。每个线程都应该提取每个帐户一次。第一次线程同步,但第二次没有。所以账户之间肯定是有区别的,被同步线程和非同步线程撤回的。并且对于不同数量的用户和线程,差异肯定是不同的。但在我的应用程序中,我只有 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所有线程都可以访问。我在任何地方都看不到您正在创建该列表的每个线程版本。命名约定表明它在类范围内。

如果该列表不是每个线程的,那将大大有助于解释为什么您的数据总是相同的。您在这里也有一个真正的竞争条件,因为您正在从多个线程更新列表。

Assuning_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情况下,如果线程已经锁定了用户,则第二个线程将无法进行更新。在第二种情况下,第二个线程将等待第一个线程完成,然后进行更新。无论哪种情况,都使用Monitorlock阻止并发更新。

尽管如此,如果多个线程可以同时执行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情况下,您启动一​​个线程,然后等待它完成,然后再启动另一个线程。您的代码不是多线程的。在“不同步”的情况下,您正在使用 alock来防止并发更新。因此,在一种情况下,您一次只运行一个线程来防止并发更新,而在另一种情况下,您通过使用lock. 不会有任何区别。

其他值得注意的是,您随机选择用户的方法效率非常低,并且可能是您遇到的问题的一部分。基本上你正在做的是选择一个随机数并检查它是否在列表中。如果是,你再试一次,等等。而且这个名单还在不断增长。快速实验表明,我必须生成 7,000 个介于 0 和 1,000 之间的随机数,然后才能获得所有这些随机数。因此,您的线程花费大量时间试图找到下一个未使用的帐户,这意味着它们同时处理同一个用户帐户的可能性较小。

你需要做三件事。首先,更改您的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.MillisecondRandom构造函数。只需调用new Random()Environment.TickCount用于种子。除非您真的想将种子限制在 0 到 1,000 之间的数字。

于 2013-10-24T16:07:54.703 回答