2013-10-24 4 views
0

У меня есть задача показать разницу между синхронизированным и несинхронизированным многопоточным процессом. Поэтому я написал приложение, имитирующее вывод денег с банковских счетов клиентов. Каждое из нескольких потоков выбирает случайного пользователя и снимает деньги со счета. Каждый поток должен удалять каждую учетную запись один раз. В первый раз потоки синхронизируются, но во второй раз они не являются. Таким образом, должна быть разница между учетными записями, снятыми синхронными и несинхронизированными потоками. И разница должна отличаться для разных пользователей и потоков. Но в моем приложении у меня есть разница только для 1000 потоков. Поэтому мне нужно, чтобы результаты несинхронизированных потоков сильно отличались от тех, которые были синхронизированы. Класс пользователя:Многопоточный доступ к списку C#

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

Я изменил ВЫВЕСТИ класс таким образом, bc проблема могла заключаться в создании потоков отдельно от делегата:

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 до завершения.

+1

Если вы пишете код, чтобы показать разницу, это синхронный и асинхронный код, но вы должны искусственно раздуть разницу между этими двумя методами, тогда ваш пример неверен. – Gusdor

+2

Я бы предположил, что несинхронизированный доступ к контейнерам, не связанным с потоками, дает непредсказуемые результаты, которые иногда включают в себя правильные результаты. – gap

+0

Какая версия рамки? .NET 4.5 предоставляет новую поточную сборку для одновременного доступа. – Fals

ответ

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 второй поток не сможет выполнить свое обновление, если поток уже заблокирован пользователем. Во втором случае второй поток будет ждать завершения первого, а затем выполнит обновление. В любом случае использование 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 для предотвращения одновременных обновлений. Таким образом, в одном случае вы предотвращаете одновременные обновления за счет запуска только одного потока за раз, а в другом случае вы предотвращаете одновременные обновления с помощью lock. Не будет никакой разницы.

Что-то еще стоит отметить, что ваш метод случайного выбора пользователя очень неэффективен и может быть частью проблемы, которую вы видите. В основном, вы делаете выбор случайного числа и проверяете, есть ли он в списке. Если да, попробуйте еще раз и т. Д. И список продолжает расти. Быстрое экспериментирование показывает, что я должен генерировать 7000 случайных чисел от 0 до 1000, прежде чем я получу их все. Таким образом, ваши потоки тратят огромное количество времени на поиск следующей неиспользованной учетной записи, а это значит, что у них меньше правдоподобности для обработки одной и той же учетной записи пользователя одновременно.

Вам нужно сделать три вещи. Во-первых, изменить метод 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 случайных чисел.

Пару nitpicks:

Я понятия не имею, почему вы имеете Thread.Sleep вызов метода Withdraw. Какую выгоду вы думаете, что она дает?

Нет никакой реальной причины для передачи DateTime.Now.Millisecond в конструктор Random. Просто звоните new Random() будет использовать Environment.TickCount для семян. Если вы действительно не хотите ограничить количество семян цифрами от 0 до 1000.

+1

Возможно, стоит отметить, что можно было бы обострить эффекты, разбив математическую часть кода на несколько этапов с задержками между имитацией перерывов. Например, прочитайте значение FinishSync, сон, вычитайте сумму, сон, напишите новое значение FinishSync. – William

+0

Уильям прав, но я удалил его. Я как-то выложил методы «Withdrawing» и «Withdraw» точно так же, как вы сказали, но все же нет никакой разницы между результатами. В конструкторе Withdrawer создан новый список _used. И каждая нить имеет свой собственный выход. – harrr

+0

@ user2502075: Смотрите мое обновление. –

Смежные вопросы