Я действительно не могу понять, как волокна будут решать вашу проблему, поскольку они в основном не обеспечивают средства для сокращения конфликтов на ресурсе общей памяти.
Я бы предпочел сосредоточиться на стратегии снижения раздор на ресурсы, уменьшить дублированный вычисления и уменьшить нить ресурс использования с 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, а не только многопоточное.
Есть несколько интересных обсуждений по волокнам здесь, когда происходит переполнение стека, попробуйте найти «C# с помощью волокон», они отправляют в MSDN несколько статей, которые, вероятно, могут помочь. –
@Sabrina_cs - спасибо за ввод. Я до сих пор наслаждался чтением некоторых обсуждений по этому вопросу и, вероятно, продолжу это делать. То, с чем я сейчас борюсь, - это четкое сравнение между ними. Еще раз спасибо за ввод. –
Волокна представляют собой сложную оптимизацию, которая не делает ничего для решения вашей задачи задачи, которая заключается в том, как обрабатывать общие данные. Один гигантский замок - это плохое решение, потому что оно не масштабируется, поэтому вам потребуется более тонкая блокировка и все, что влечет за собой. – Voo