Как я должен увеличить число для сценария с циклической нарезкой с наименьшим количеством конфликтов?
Если много тем звонят GetNextNumber
одновременно со следующим кодом, GetNextNumber
вернет на 1 больше раз, чем любые другие числа.
private class RoundRobbinNumber
{
private int _maxNumbers = 10;
private int _lastNumber;
private RoundRobbinNumber(int maxNumbers)
{
_maxNumbers = maxNumbers;
}
public int GetNextNumber()
{
int nextNumber = Interlocked.Increment(ref _lastNumber);
if (_lastNumber > _maxNumbers)
{
Interlocked.CompareExchange(ref _lastNumber, 1, _maxNumbers);
nextNumber = 1;
}
return nextNumber;
}
}
Есть ли способ сбросить _lastNumber
вернуться к одному и надежно вернуть увеличенный номер для каждого вызывающего потока GetNextNumber()
без необходимости использовать замок?
4 ответа
Хитрость заключается в том, чтобы выполнять операцию в цикле, пока она не будет успешной. Я приведу общий шаблон для этого подхода в своем ответе здесь.
public int GetNextNumber()
{
int initial, computed;
do
{
initial = _lastNumber;
computed = initial + 1;
computed = computed > _maxNumbers ? computed = 1 : computed;
}
while (Interlocked.CompareExchange(ref _lastNumber, computed, initial) != initial);
return computed;
}
Ответ Андрея без условных утверждений:
using System;
namespace Utils
{
public class RoundRobinCounter
{
private int _max;
private int _currentNumber = 0;
public RoundRobinCounter(int max)
{
_max = max;
}
public int GetNext()
{
uint nextNumber = unchecked((uint)System.Threading.Interlocked.Increment(ref _currentNumber));
int result = (int)(nextNumber % _max);
return result;
}
}
}
А вот скрипта.net запускает этот код.
Не уверен, что если кому-то поможет, но это может быть еще проще:
class RoundRobinNumber
{
private int _maxNumbers = 10;
private int _lastNumber = 0;
public RoundRobinNumber(int maxNumbers)
{
_maxNumbers = maxNumbers;
}
public int GetNextNumber()
{
int nextNumber = Interlocked.Increment(ref _lastNumber);
int result = nextNumber % _maxNumbers;
return result >= 0 ? result : -result;
}
}
Обычно циклический перебор используется для выбора элемента из коллекции. Основываясь на ответе Алекса, я сделалRoundRobinCollection
вариант.
public class RoundRobinCollection<T>
{
private readonly ReadOnlyCollection<T> _collection;
private int _currentNumber = -1;
public RoundRobinCollection(IEnumerable<T> enumerable)
{
_collection = new List<T>(enumerable).AsReadOnly();
if (!_collection.Any())
{
throw new InvalidOperationException("Cannot use empty collection for RoundRobinCollection.");
}
}
public T GetNext()
{
var index = GetNextIndex();
return _collection[index];
}
private int GetNextIndex()
{
// This increments the currentNumber in a Thread-safe way, and deals with exceeding int.MaxValue properly
var nextNumber = unchecked((uint)Interlocked.Increment(ref _currentNumber));
return (int)(nextNumber % _collection.Count);
}
}
Пример использования:
public class SomeClient
{
private readonly RoundRobinCollection<ServerConfig> _serverConfigs;
public SomeClient(List<ServerConfig> serverConfigs)
{
_serverConfigs = new RoundRobinCollection<ServerConfig>(serverConfigs);
}
public void DoSomething(){
var serverConfig = _serverConfigs.GetNext();
// Do something with current serverConfig
}
}