将字典重构为 ConcurrentDictionary

发布于 2024-11-07 18:41:02 字数 3345 浏览 2 评论 0原文

我想让我的代码多线程化,因此我需要将字典更改为ConcurrentDictionary。我读到了关于 ConcurrentDictionary 的内容,检查了一些示例,但我仍然需要帮助:

这是原始代码(对于单线程)

private IDictionary<string, IDictionary<string, Task>> _tasks;

public override IDictionary<string, IDictionary<string, Task>> Tasks
{
    get
    {
        // return dictionary from cache unless too old
        // concurrency!! (null check)
        if (_tasks != null && (DateTime.Now - _lastTaskListRefreshDateTime < TimeSpan.FromSeconds(30)))
        {
            return _tasks;
        }

        // reload dictionary from database
        _tasks = new Dictionary<string, IDictionary<string, Task>>();

        // find returns an IEnumerable<Task>
        var tasks = Find<Task>(null, DependencyNode.TaskForCrawler).Cast<Task>();

        // build hierarchical dictionary from flat IEnumerable
        // concurrency!!
        foreach (var t in tasks)
        {

            if (_tasks.ContainsKey(t.Area.Key))
            {
                if (_tasks[t.Area.Key] == null)
                {
                    _tasks[t.Area.Key] = new Dictionary<string, Task>();
                }

                if (!_tasks[t.Area.Key].ContainsKey(t.Key))
                {
                    _tasks[t.Area.Key].Add(t.Key, t);
                }
            }
            else
            {
                _tasks.Add(t.Area.Key, new Dictionary<string, Task> { { t.Key, t } });
            }
        }

        _lastTaskListRefreshDateTime = DateTime.Now;
        return _tasks;
    }

    set
    {
        _tasks = value;
    }
}

这是我想到的:

private ConcurrentDictionary<string, ConcurrentDictionary<string, Task>> _tasks = new ConcurrentDictionary<string, ConcurrentDictionary<string, Task>>();

public override ConcurrentDictionary<string, ConcurrentDictionary<string, Task>> Tasks
{
    get
    {
        // use cache
        // concurrency?? (null check)
        if (!_tasks.IsEmpty && (DateTime.Now - _lastTaskListRefreshDateTime < TimeSpan.FromSeconds(30)))
        {
            return _tasks;
        }

        // reload
        var tasks = Find<Task>(null, DependencyNode.TaskForCrawler).Cast<Task>();

        foreach (var task in tasks)
        {
            var t = task; // inner scope for clousure
            var taskKey = t.Key;
            var areaKey = t.Area.Key;

            var newDict = new ConcurrentDictionary<string, Task>();
            newDict.TryAdd(taskKey, t);

            _tasks.AddOrUpdate(areaKey, newDict, (k, v) => {
                                                    // An dictionary element if key=areaKey already exists
                                                    // extend and return it.
                                                    v.TryAdd(taskKey, t);
                                                    return v;
                                                   });
        }

        _lastTaskListRefreshDateTime = DateTime.Now;
        return _tasks;
    }
}

我不太确定就是这样,特别是我非常确定 IsEmpty 检查不是线程安全的,因为 _tasks 可能已在 IsEmpty 检查和这<代码>&& ... 部分或 return _tasks 部分。我必须手动锁定此支票吗?我需要双重锁(空检查>锁>空检查)吗?

I want to make my code multithreadable, therefor i need to change a Dictionary into a ConcurrentDictionary. I read about the ConcurrentDictionary, checked some example, but still I need a hand on this:

Here is the original code (for single thread)

private IDictionary<string, IDictionary<string, Task>> _tasks;

public override IDictionary<string, IDictionary<string, Task>> Tasks
{
    get
    {
        // return dictionary from cache unless too old
        // concurrency!! (null check)
        if (_tasks != null && (DateTime.Now - _lastTaskListRefreshDateTime < TimeSpan.FromSeconds(30)))
        {
            return _tasks;
        }

        // reload dictionary from database
        _tasks = new Dictionary<string, IDictionary<string, Task>>();

        // find returns an IEnumerable<Task>
        var tasks = Find<Task>(null, DependencyNode.TaskForCrawler).Cast<Task>();

        // build hierarchical dictionary from flat IEnumerable
        // concurrency!!
        foreach (var t in tasks)
        {

            if (_tasks.ContainsKey(t.Area.Key))
            {
                if (_tasks[t.Area.Key] == null)
                {
                    _tasks[t.Area.Key] = new Dictionary<string, Task>();
                }

                if (!_tasks[t.Area.Key].ContainsKey(t.Key))
                {
                    _tasks[t.Area.Key].Add(t.Key, t);
                }
            }
            else
            {
                _tasks.Add(t.Area.Key, new Dictionary<string, Task> { { t.Key, t } });
            }
        }

        _lastTaskListRefreshDateTime = DateTime.Now;
        return _tasks;
    }

    set
    {
        _tasks = value;
    }
}

Here is what I came up with:

private ConcurrentDictionary<string, ConcurrentDictionary<string, Task>> _tasks = new ConcurrentDictionary<string, ConcurrentDictionary<string, Task>>();

public override ConcurrentDictionary<string, ConcurrentDictionary<string, Task>> Tasks
{
    get
    {
        // use cache
        // concurrency?? (null check)
        if (!_tasks.IsEmpty && (DateTime.Now - _lastTaskListRefreshDateTime < TimeSpan.FromSeconds(30)))
        {
            return _tasks;
        }

        // reload
        var tasks = Find<Task>(null, DependencyNode.TaskForCrawler).Cast<Task>();

        foreach (var task in tasks)
        {
            var t = task; // inner scope for clousure
            var taskKey = t.Key;
            var areaKey = t.Area.Key;

            var newDict = new ConcurrentDictionary<string, Task>();
            newDict.TryAdd(taskKey, t);

            _tasks.AddOrUpdate(areaKey, newDict, (k, v) => {
                                                    // An dictionary element if key=areaKey already exists
                                                    // extend and return it.
                                                    v.TryAdd(taskKey, t);
                                                    return v;
                                                   });
        }

        _lastTaskListRefreshDateTime = DateTime.Now;
        return _tasks;
    }
}

I'm not so sure this is it, in particular i am quite sure that the IsEmpty check is not threadsafe since the _tasks may have been initialized between the IsEmpty check and the && ... part or the return _tasks part. Do I have to lock this check manually? Do i need a double lock (null check > lock > null check) ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

爱给你人给你 2024-11-14 18:41:02

你的担心是有道理的。 Tasks 属性 getter 不是线程安全的。这里有几个问题。

首先,像您一样,从一个线程调用 IsEmpty 与从另一个线程删除项目之间存在竞争。 getter 可以返回一个空字典。

其次,在 if 检查中读取 _lastTaskListRefreshDateTime 与 getter 末尾的赋值之间存在竞争。即使这些操作是原子的(至少在 32 位平台上它们不能是原子的,因为 DateTime 是 64 位),仍然存在一个微妙的内存屏障问题,因为没有像 volatile< 这样的同步机制/code> 在代码中很明显。

第三,与我上面的解释类似,_tasks引用还存在另一个内存障碍问题。一个线程可以调用 setter,而另一个线程则调用 getter。由于不存在内存屏障,CLR 或硬件可以自由地优化读取和写入,从而使 setter 中所做的更改对 getter 不可见。此问题不一定会导致任何问题,但我敢打赌这是未预料到的行为。由于没有其他背景可供分析,我不能说任何一种方式。

Your concern is justified. The Tasks property getter is not thread-safe. There are a few issues here.

First, like you side, there is a race between a call to IsEmpty from one thread and the removal of item from another thread. The getter could return an empty dictionary.

Second, there is a race between the read of _lastTaskListRefreshDateTime in the if check and the assignment at the end of the getter. Even if these operations are atomic (which they cannot be at least on 32-bit platforms since DateTime is 64-bits) there is still a subtle memory barrier issue since no synchronization mechanisms like volatile are apparent in the code.

Third, similar to my explanation above, there is another memory barrier problem with _tasks reference. One thread could call the setter while another is calling the getter. Since no memory barrier is present the CLR or hardware are free to optimize the reads and writes in such a manner that the changes made in the setter are not visible to the getter. This issue might not necessarily cause any problems, but I bet it is behavior that was not anticipated. With no other context for analysis I cannot say either way.

往事随风而去 2024-11-14 18:41:02

ConcurrentDictionary 仅保证对字典的读取和写入不会互相遍历,而 Dictionary 类则不会这样做。 ConcurrentDictionary 中的线程安全并不使您的代码线程安全,它仅确保其代码是线程安全的。在这种情况下,您需要在 getter 中加一把锁。

The ConcurrentDictionary only guarantees that reads and writes into the dictionary will not walk all over each other, something the Dictionary class does not do. The thread safety in the ConcurrentDictionary does not make your code thread safe, it only ensures its code is thread safe. Since this is the case you will need a lock in your getter.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文