2015-09-23 51 views
2

我目前正在使用一个c#应用程序,它将作为多人游戏的服务器端工作,而我对于如何处理多线程问题略有不确定。在我继续之前,可能值得一提的是,我对这个主题颇为陌生。独占锁与线程光纤

问题

之一服务器端应用程序的要求是,它应包含应用程序特定数据,诸如已连接到服务器上的对等信息,以及它们的位置和等等。问题是如果没有某种形式的线程安全机制,两个请求可能会读写同一条数据,这显然是有问题的。

解决问题

直到现在,解决我只是被包裹的锁块内的每个要求,确保每个请求发生在一个串行顺序的问题,使数据永远只一次被同伴操纵。

最近,在对这个话题做了一些研究之后,我介绍了fibers的想法,以及设置“光纤池”的方法,允许将动作排队到单一光纤上的能力,作为另一种尝试确保以串行顺序发生请求。

问题

我穿线和这些类型的主题的知识是相当有限。我很想知道更多关于这个话题的信息,特别是我很想知道任何一种解决方案的优缺点,并最终知道我应该采用哪条路线。

任何帮助将不胜感激。

+0

在堆栈溢出中,有一些关于光纤的有趣讨论,请尝试搜索他们发送给MSDN几篇文章的“c#使用光纤”,这可能会有所帮助。 –

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

围棋异步,而不是多线程

即使你正在做的多线程,你可能会浪费线程资源,而且线程实际上很昂贵,因为为每个线程分配了栈内存并且由于上下文切换。

设计良好的异步应用程序实际上使用的线程数与系统中的CPU核数一样多。

研究让你的应用程序异步,而不仅仅是多线程。

+0

我真的很喜欢这个答案。它几乎为我清除了一切。你的例子是优雅和信息。感谢您花时间做到这一点。 –