如何动态锁定字符串但从内存中删除锁定对象

时间:2015-11-18 17:31:34

标签: c# multithreading

我有以下情况:

我的项目中有很多线程,每个线程按时间处理一个“密钥”。

两个线程不能同时处理相同的“密钥”,但我的项目处理A LOOOOOT OF KEYS,所以我无法将“密钥”存储在内存中,我需要在内存上存储一个线程正在处理一个“密钥”,如果另一个线程试图处理相同的“密钥”,该线程将在lock子句中等待。

现在我有以下结构:

   public class Lock
    {
        private static object _lockObj = new object();
        private static List<object> _lockListValues = new List<object>();

        public static void Execute(object value, Action action)
        {
            lock (_lockObj)
            {
                if (!_lockListValues.Contains(value))
                    _lockListValues.Add(value);
            }

            lock (_lockListValues.First(x => x.Equals(value)))
            {
                action.Invoke();
            }
        }
    }

工作正常,问题是密钥没有从内存中删除。最大的麻烦是多线程功能,因为任何时候都可以处理“密钥”。

如果没有独立于键的全局锁定,我怎么能解决这个问题?

2 个答案:

答案 0 :(得分:2)

抱歉,但不,这不是应该做的。

首先,您谈论密钥,但是您将密钥存储为List中的类型对象,然后使用LINQ进行搜索以从列表中获取密钥。

对于那种东西,这里是字典。

其次,对象模型,通常最好在某些类周围实现某些对象的锁定,使其变得美观干净:

像:

using System.Collections.Concurrent;


public LockedObject<T>
{
    public readonly T data;
    public readonly int id;
    private readonly object obj = new object();
    LockedObject(int id, T data)
    {
        this.id = id;
        this.data = data;

    }

    //Usually, if you have Action related to some data,
    //it is better to receive
    //that data as parameter

    public void InvokeAction(Action<T> action)
    {
        lock(obj)
        {
            action(data);
        }
    }

}

//Now it is a concurrently safe object applying some action
//concurrently on given data, no matter how it is stored.
//But still, this is the best idea:


ConcurrentDictionary<int, LockedObject<T>> dict =
new ConcurrentDictionary<int, LockedObject<T>>();

//You can insert, read, remove all object's concurrently.

但是,最好的事情还未到来! :)你可以轻松锁定它!

<强> EDIT1:

ConcurrentInvoke,类似于集合的字典,用于对数据进行并发安全的调用操作。在给定的密钥上,当时只能有一个动作。

using System;
using System.Threading;
using System.Collections.Concurrent;


public class ConcurrentInvoke<TKey, TValue>
{
    //we hate lock() :)

    private class Data<TData>
    {
        public readonly TData data;
        private int flag;
        private Data(TData data)
        {
            this.data = data;
        }
        public static bool Contains<TTKey>(ConcurrentDictionary<TTKey, Data<TData>> dict, TTKey key)
        {
            return dict.ContainsKey(key);
        }
        public static bool TryAdd<TTKey>(ConcurrentDictionary<TTKey, Data<TData>> dict, TTKey key, TData data)
        {
            return dict.TryAdd(key, new Data<TData>(data));
        }
        // can not remove if,
        // not exist,
        // remove of the key already in progress,
        // invoke action of the key inprogress
        public static bool TryRemove<TTKey>(ConcurrentDictionary<TTKey, Data<TData>> dict, TTKey key, Action<TTKey, TData> action_removed = null)
        {
            Data<TData> data = null;
            if (!dict.TryGetValue(key, out data)) return false;

            var access = Interlocked.CompareExchange(ref data.flag, 1, 0) == 0;
            if (!access) return false;

            Data<TData> data2 = null;
            var removed = dict.TryRemove(key, out data2);

            Interlocked.Exchange(ref data.flag, 0);

            if (removed && action_removed != null) action_removed(key, data2.data);
            return removed;
        }
        // can not invoke if,
        // not exist,
        // remove of the key already in progress,
        // invoke action of the key inprogress
        public static bool TryInvokeAction<TTKey>(ConcurrentDictionary<TTKey, Data<TData>> dict, TTKey key, Action<TTKey, TData> invoke_action = null)
        {
            Data<TData> data = null;
            if (invoke_action == null || !dict.TryGetValue(key, out data)) return false;

            var access = Interlocked.CompareExchange(ref data.flag, 1, 0) == 0;
            if (!access) return false;

            invoke_action(key, data.data);

            Interlocked.Exchange(ref data.flag, 0);
            return true;
        }
    }

    private 
    readonly
    ConcurrentDictionary<TKey, Data<TValue>> dict =
    new ConcurrentDictionary<TKey, Data<TValue>>()
    ;

    public bool Contains(TKey key)
    {
        return Data<TValue>.Contains(dict, key);
    }
    public bool TryAdd(TKey key, TValue value)
    {
        return Data<TValue>.TryAdd(dict, key, value);
    }
    public bool TryRemove(TKey key, Action<TKey, TValue> removed = null)
    {
        return Data<TValue>.TryRemove(dict, key, removed);
    }
    public bool TryInvokeAction(TKey key, Action<TKey, TValue> invoke)
    {
        return Data<TValue>.TryInvokeAction(dict, key, invoke);
    }
}




ConcurrentInvoke<int, string> concurrent_invoke = new ConcurrentInvoke<int, string>();

concurrent_invoke.TryAdd(1, "string 1");
concurrent_invoke.TryAdd(2, "string 2");
concurrent_invoke.TryAdd(3, "string 3");

concurrent_invoke.TryRemove(1);

concurrent_invoke.TryInvokeAction(3, (key, value) =>
{
    Console.WriteLine("InvokingAction[key: {0}, vale: {1}", key, value);
});

答案 1 :(得分:1)

我修改了我在another question中发布的KeyedLock类,以在内部使用Monitor类而不是SemaphoreSlim s。我希望使用专门的机制进行同步锁定会提供更好的性能,但实际上看不到任何区别。我无论如何都会发布它,因为它具有附加的便利功能,即可以使用using语句自动释放锁。在同步锁定的情况下,此功能不会增加任何重大开销,因此没有理由忽略它。

KeyedMonitor类在内部仅存储当前正在使用的锁定对象,以及一小部分已释放并可以重用的锁定对象。在大量使用情况下,该池可显着减少内存分配。

public class KeyedMonitor<TKey>
{
    private readonly Dictionary<TKey, (object, int)> _perKey;
    private readonly Stack<object> _pool;
    private readonly int _poolCapacity;

    public KeyedMonitor(IEqualityComparer<TKey> keyComparer = null,
        int poolCapacity = 10)
    {
        _perKey = new Dictionary<TKey, (object, int)>(keyComparer);
        _pool = new Stack<object>(poolCapacity);
        _poolCapacity = poolCapacity;
    }

    public ExitToken Enter(TKey key)
    {
        var locker = GetLocker(key);
        Monitor.Enter(locker);
        return new ExitToken(this, key);
    }

    public bool TryEnter(TKey key, int millisecondsTimeout)
    {
        var locker = GetLocker(key);
        var acquired = Monitor.TryEnter(locker, millisecondsTimeout);
        if (!acquired) ReleaseLocker(key, withMonitorExit: false);
        return acquired;
    }

    public void Exit(TKey key) => ReleaseLocker(key, withMonitorExit: true);

    private object GetLocker(TKey key)
    {
        object locker;
        lock (_perKey)
        {
            if (_perKey.TryGetValue(key, out var entry))
            {
                int counter;
                (locker, counter) = entry;
                counter++;
                _perKey[key] = (locker, counter);
            }
            else
            {
                bool rented; lock (_pool) rented = _pool.TryPop(out locker);
                if (!rented) locker = new object();
                _perKey[key] = (locker, 1);
            }
        }
        return locker;
    }

    private void ReleaseLocker(TKey key, bool withMonitorExit)
    {
        object locker; int counter;
        lock (_perKey)
        {
            if (_perKey.TryGetValue(key, out var entry))
            {
                (locker, counter) = entry;
                counter--;
                if (counter == 0)
                    _perKey.Remove(key);
                else
                    _perKey[key] = (locker, counter);
            }
            else
            {
                throw new InvalidOperationException("Key not found.");
            }
        }
        if (withMonitorExit) Monitor.Exit(locker);
        if (counter == 0)
            lock (_pool) if (_pool.Count < _poolCapacity) _pool.Push(locker);
    }

    public readonly struct ExitToken : IDisposable
    {
        private readonly KeyedMonitor<TKey> _parent;
        private readonly TKey _key;

        public ExitToken(KeyedMonitor<TKey> parent, TKey key)
        {
            _parent = parent; _key = key;
        }

        public void Dispose() => _parent?.Exit(_key);
    }
}

用法示例:

var locker = new KeyedMonitor<string>();

using (locker.Enter("Hello"))
{
    DoSomething(); // with the "Hello" resource
}

尽管KeyedMonitor类是线程安全的,但它不如直接使用lock语句那么健壮,因为在ThreadAbortException的情况下它没有提供弹性。中止的线程可能会使类处于损坏的内部状态。我认为这不是什么大问题,因为Thread.Abort方法在当前版本的.NET平台(.NET 5)中已经过时了。