我真的无法弄清楚纤程如何解决您的问题,因为它们基本上没有提供减少共享内存资源争用的方法。
我宁愿专注于减少资源争用、减少重复计算以及通过异步处理减少线程资源使用的策略。
在所有请求处理之上使用全局锁基本上将所有处理减少到单个活动线程。作为替代方案,您可以尝试仅使用每个资源的锁来减少锁定。
免责声明:这里提供的示例代码绝不是生产质量,这里只是为了说明概念。
减少争用
当您仅为特定操作锁定相关数据的某些区域时,您可以提出一种细粒度的锁定策略。
下面是一个排序游戏示例,它定义了简单的规则:每个玩家抓取列表中的一个项目,然后将其与下一个交换,如果左边的项目小于右边的项目。当所有项目都排序后,游戏结束。没有人会赢,只是很有趣。
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 内核一样多的线程。
考虑使您的应用程序异步,而不仅仅是多线程。