1

我正在开发一个 WCF 应用程序,该应用程序托管一个自定义对象供许多客户端访问。它基本上可以正常工作,但是因为我需要同时处理数千个客户端,所以我需要该服务能够处理并发读取调用(更新将不频繁)。我通过在更新对象时锁定私有字段来添加一些线程安全性。

[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public sealed class TestServerConfig : ConfigBase, ITestInterface
{
    private object updateLock = new object();

    private SortedList<string, DateTime> dates = new SortedList<string, DateTime>();

    public DateTime GetDate(string key)
    {
        if (this.dates.ContainsKey(key))
        {
            return this.dates[key];
        }
        else
        {
            return DateTime.MinValue;
        }
    }
    public void SetDate(string key, DateTime expirationDate)
    {
        lock (this.updateLock)
        {
            if (this.dates.ContainsKey(key))
            {
                this.dates[key] = expirationDate;
            }
            else
            {
                this.dates.Add(key, expirationDate);
            }
        }
    }
}

我的问题是如何在不锁定的情况下使 GetDate 线程安全,以便可以执行对 GetDate 的并发调用,但是当在检查之后但在读取值之前从集合中删除值时,异常不会随机发生。

捕捉异常并处理它是可能的,但我更愿意继续处理它。

有任何想法吗?

4

4 回答 4

3

有一个专门为此设计的锁,ReaderWriterLockSlim(如果您使用的版本低于 .NET 4.0 ,则为ReadWriterLock )

此锁允许并发读取,但在发生写入时锁定读取(和其他写入)。

[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public sealed class TestServerConfig : ConfigBase, ITestInterface
{
    private ReaderWriterLockSlim updateLock = new ReaderWriterLockSlim();

    private SortedList<string, DateTime> dates = new SortedList<string, DateTime>();


    public DateTime GetDate(string key)
    {
        try
        {   
            this.updateLock.EnterReadLock();
            if (this.dates.ContainsKey(key))
            {
                return this.dates[key];
            }
            else
            {
                return DateTime.MinValue;
            }
        }
        finally
        {
            this.updateLock.ExitReadLock();
        }
    }
    public void SetDate(string key, DateTime expirationDate)
    {
        try
        {
            this.updateLock.EnterWriteLock();

            if (this.dates.ContainsKey(key))
            {
                this.dates[key] = expirationDate;
            }
            else
            {
                this.dates.Add(key, expirationDate);
            }
        }
        finally
        {
             this.updateLock.ExitWriteLock();
        }
    }
}

还有支持超时的锁的“尝试”版本,您只需检查返回的布尔值以查看您是否获得了锁。


更新:另一个解决方案是使用ConcurrentDictionary,这根本不需要任何锁。ConcurrentDictionary 在内部使用锁,但它们的寿命比您可以使用的要短,而且微软也有可能使用某种形式的不安全方法来进一步优化它,我不知道他们在内部使用什么样的锁。

您将需要进行一些重写以使您的操作具有原子性

[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public sealed class TestServerConfig : ConfigBase, ITestInterface
{
    private ConcurrentDictionary<string, DateTime> dates = new ConcurrentDictionary<string, DateTime>();

    public DateTime GetDate(string key)
    {
        DateTime result;

        if (this.dates.TryGetValue(key, out result))
        {
            return result;
        }
        else
        {
            return DateTime.MinValue;
        }
    }
    public void SetDate(string key, DateTime expirationDate)
    {
        this.dates.AddOrUpdate(key, expirationDate, (usedKey, oldValue) => expirationDate);
    }
}

UPDATE2:出于好奇,我查看了引擎盖下的内容ConcurrentDictionary,它所做的只是锁定元素的一组存储桶,因此如果两个对象也共享相同的哈希存储桶锁,则只会出现锁争用。

通常有Environment.ProcessorCount * 4锁桶,但您可以使用设置concurrencyLevel的构造函数手动设置它。

这是它如何决定使用哪个锁

private void GetBucketAndLockNo(int hashcode, out int bucketNo, out int lockNo, int bucketCount, int lockCount)
{
    bucketNo = (hashcode & 2147483647) % bucketCount;
    lockNo = bucketNo % lockCount;
}

lockCount等于concurrencyLevel构造函数中的集合。

于 2013-07-02T18:10:41.883 回答
1

我建议您使用ReaderWriterLockSlim文档,该文档提供的示例几乎正是您想要的。(http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim.aspx

但是,像这样:

public DateTime GetDate(string key)
{
    cacheLock.EnterReadLock();
    try
    {
        if (this.dates.ContainsKey(key))
        {
            return this.dates[key];
        }
        else
        {
            return DateTime.MinValue;
        }
    }
    finally
    {
        cacheLock.ExitReadLock();
    }
}
public void SetDate(string key, DateTime expirationDate)
{
    cacheLock.EnterWriteLock();
    try
    {
        if (this.dates.ContainsKey(key))
        {
            this.dates[key] = expirationDate;
        }
        else
        {
            this.dates.Add(key, expirationDate);
        }
    }
    finally
    {
        cacheLock.ExitWriteLock();
    }
}

ReaderWriterLockSlim比使用 a 性能更高,lock并且区分读取和写入,因此如果没有写入发生,则读取变为非阻塞。

于 2013-07-02T18:09:48.240 回答
0

我有同样的情况并使用了 ReaderWriterLockSlim 并在这些情况下工作。

于 2013-07-03T19:23:03.210 回答
0

如果您真的无法为读取锁定,您可以(在锁定内)制作列表的副本,相应地更新它,然后替换旧列表。现在可能发生的最糟糕的事情是某些读取会有点过时,但它们永远不应该抛出。

lock (this.updateLock)
{
    var temp = <copy list here>
    if (temp.ContainsKey(key))
    {
        temp[key] = expirationDate;
    }
    else
    {
        temp.Add(key, expirationDate);
    }

    this.dates = temp;
}

效率不高,但如果你不经常这样做,那可能没关系。

于 2013-07-02T18:10:58.537 回答