2015-09-23 2 views
2

В настоящее время я работаю с приложением C#, которое будет работать как серверная часть многопользовательской игры, и я немного не уверен, как мне следует обрабатывать многопоточные проблемы. Прежде чем продолжить, вероятно, стоит упомянуть, что я довольно новичок в этой теме.Эксклюзивный замок против волокна нити

Проблема

Одним из требований приложения на стороне сервера является то, что она должна содержать данные приложения, такие как информацию о сверстниках, которые подключили к серверу, а также их расположение и скоро. Проблема в том, что без какой-либо формы потокобезопасного механика, возможно, что два запроса будут считываться и записываться в один и тот же фрагмент данных, что, очевидно, проблематично.

Решение проблемы

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

Недавно, после некоторых исследований по этой теме, я познакомился с идеей fibers и способом создания «пула волокон», позволяющего размещать очереди на одном волокне в качестве другого способа убедитесь, что запросы выполняются в последовательном порядке.

Вопрос

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

Любая помощь была бы принята с благодарностью.

+0

Есть несколько интересных обсуждений по волокнам здесь, когда происходит переполнение стека, попробуйте найти «C# с помощью волокон», они отправляют в MSDN несколько статей, которые, вероятно, могут помочь. –

+0

@Sabrina_cs - спасибо за ввод. Я до сих пор наслаждался чтением некоторых обсуждений по этому вопросу и, вероятно, продолжу это делать. То, с чем я сейчас борюсь, - это четкое сравнение между ними. Еще раз спасибо за ввод. –

+2

Волокна представляют собой сложную оптимизацию, которая не делает ничего для решения вашей задачи задачи, которая заключается в том, как обрабатывать общие данные. Один гигантский замок - это плохое решение, потому что оно не масштабируется, поэтому вам потребуется более тонкая блокировка и все, что влечет за собой. – Voo

ответ

1

Я действительно не могу понять, как волокна будут решать вашу проблему, поскольку они в основном не обеспечивают средства для сокращения конфликтов на ресурсе общей памяти.

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

Использование глобальной блокировки поверх всей обработки запроса в основном сводит всю обработку к одному потоку в реальном времени. В качестве альтернативы вы можете попытаться уменьшить блокировку с помощью блокировок только на ресурс.

Disclamer: Пример кода, представленный здесь, отнюдь не является производственным качеством, он здесь только для иллюстрации концепций.

Уменьшить раздор

Вы можете прийти с гранулированной стратегией блокировки, когда вы фиксируете только некоторую область данных в вопросе для конкретной операции.

Ниже приведен пример Сортировочной игры, которая определяет простые правила: Каждый игрок захватывает элемент в списке и заменяет его следующим, если левый элемент меньше права. Игра заканчивается, когда сортируются все предметы. Никто не выигрывает, просто весело.

using System; 
using System.Threading; 
using System.Collections.Generic; 
using System.Linq; 

public class Program 
{ 
    public static void Main() 
    { 
     var game = new SortingGame(); 
     var random = new Random(234); 

     // Simulate few concurrent players. 
     for (var i = 0; i < 3; i++) 
     { 
      ThreadPool.QueueUserWorkItem(o => 
      { 
       while (!game.IsSorted()) 
       { 
        var x = random.Next(game.Count() - 1); 
        game.PlayAt(x); 
        DumpGame(game); 
       }; 
      }); 
     } 

     Thread.Sleep(4000); 

     DumpGame(game); 
    } 

    static void DumpGame(SortingGame game) 
    { 
     var items = game.GetBoardSnapshot(); 

     Console.WriteLine(string.Join(",", items)); 
    } 
} 


class SortingGame 
{ 
    List<int> items; 
    List<object> lockers; 

    // this lock is taken for the entire board to guard from inconsistent reads. 
    object entireBoardLock = new object(); 

    public SortingGame() 
    { 
     const int N = 10; 

     // Initialize a game with items in random order 
     var random = new Random(1235678); 
     var setup = Enumerable.Range(0, N).Select(i => new { x = i, position = random.Next(0, 100)}).ToList(); 
     items = setup.OrderBy(i => i.position).Select(i => i.x).ToList(); 
     lockers = Enumerable.Range(0, N).Select(i => new object()).ToList(); 
    } 

    public int Count() 
    { 
     return items.Count; 
    } 

    public bool IsSorted() 
    { 
     var currentBoard = GetBoardSnapshot(); 
     var pairs = currentBoard.Zip(currentBoard.Skip(1), (a, b) => new { a, b}); 
     return pairs.All(p => p.a <= p.b); 
    } 

    public IEnumerable<int> GetBoardSnapshot() 
    { 
     lock (entireBoardLock) 
      return new List<int>(items); 
    } 

    public void PlayAt(int x) 
    { 
     // Find the resource lockers for the two adjacent cells in question 
     var locker1 = GetLockForCell(x); 
     var locker2 = GetLockForCell(x + 1); 

     // It's important to lock the resources in a particular order, same for all the contending writers and readers. 
     // These can last for a long time, but are granular, 
     // so the contention is greatly reduced. 
     // Try to remove one of the following locks, and notice the duplicate items in the result 
     lock (locker1) 
     lock (locker2) 
      { 
       var a = items[x]; 
       var b = items[x + 1]; 
       if (a > b) 
       { 
        // Simulate expensive computation 
        Thread.Sleep(100); 
        // Following is a lock to protect from incorrect game state read 
        // The lock lasts for a very short time. 
        lock (entireBoardLock) 
        { 
         items[x] = b; 
         items[x + 1] = a; 
        } 
       }   
      } 
    } 

    object GetLockForCell(int x) 
    { 
     return lockers[x]; 
    } 
} 

Исключите дублированные вычисления

Если вам нужны дорогие вычисления, чтобы быть в курсе, но не зависит от конкретного запроса, пытаясь вычислить его для каждого запроса будет просто трата ресурсов ,

Следующий подход позволяет пропустить повторное вычисление, если вычисление уже запущено для другого запроса.

Это отличается от кэширования, потому что вы на самом деле получить наилучший результат для расчета в сроки, таким образом:

void Main() 
{ 
    for (var i = 0; i < 100; i++) 
    { 
     Thread.Sleep(100); 
     var j = i; 
     ThreadPool.QueueUserWorkItem((o) => { 
      // In this example, the call is blocking becase of the Result property access. 
      // In a real async method you would be awaiting the result. 
      var result = computation.Get().Result; 

      Console.WriteLine("{0} {1}", j, result); 
     }); 
    } 
} 

static ParticularSharedComputation computation = new ParticularSharedComputation(); 

abstract class SharedComputation 
{ 
    volatile Task<string> currentWork; 
    object resourceLock = new object(); 
    public async Task<string> Get() 
    { 
     Task<string> current; 
     // We are taking a lock here, but all the operations inside a lock are instant. 
     // Actually we are just scheduling a task to run. 
     lock (resourceLock) 
     { 
      if (currentWork == null) 
      { 
       Console.WriteLine("Looks like we have to do the job..."); 
       currentWork = Compute(); 
       currentWork.ContinueWith(t => { 
        lock (resourceLock) 
         currentWork = null; 
       }); 
      } 
      else 
       Console.WriteLine("Someone is already computing. Ok, will wait a bit..."); 
      current = currentWork; 
     } 

     return await current; 
    } 

    protected abstract Task<string> Compute(); 
} 

class ParticularSharedComputation : SharedComputation 
{ 
    protected override async Task<string> Compute() 
    { 
     // This method is thread safe if it accesses only it's instance data, 
     // as the base class allows only one simultaneous entrance for each instance. 
     // Here you can safely access any data, local for the instance of this class. 
     Console.WriteLine("Computing..."); 

     // Simulate a long computation. 
     await Task.Delay(2000); 

     Console.WriteLine("Computed."); 
     return DateTime.Now.ToString(); 
    } 
} 

Go асинхр, а не только многопоточной

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

Хорошо спроектированное приложение async фактически будет использовать столько потоков, сколько в вашей системе есть ядра процессора.

Загляните в приложение async, а не только многопоточное.

+0

Мне очень нравится этот ответ. Это в значительной степени очистило все для меня. Ваши примеры элегантны и информативны. Спасибо, что нашли время для этого. –

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