我的想法是只要最高级别的集合被配置为并发/线程安全,就不必在意嵌套集合是否也是如此,因为数据将被高级别的集合锁定。
这个假设是不正确的。
假设您创建了一个包含许多常规哈希表的并发字典:
using namespace System.Collections.Concurrent
$rootDict = [ConcurrentDictionary[string,hashtable]]::new()
$rootDict
现在是线程安全的 - 不能有多个线程同时通过覆盖哈希表引用来修改'A'
条目。
- 我们添加到
$rootDict
的任何内部哈希表都不是线程安全的 - 它仍然只是一个普通的哈希表。
在PowerShell 7中,当使用ForEach-Object -Parallel
操作此类数据结构时,可以观察到这一点:
using namespace System.Collections.Concurrent
$rootDict = [ConcurrentDictionary[string,hashtable]]::new()
1..100 |ForEach-Object -Parallel {
$dict = $using:rootDict
$rootKey = $_ % 2 -eq 0 ? 'even' : 'odd'
$innerDict = $dict.GetOrAdd($rootKey, {param($key) return @{}})
Start-Sleep -Milliseconds (Get-Random -Minimum 50 -Maximum 250)
$innerDict['Counter'] += 1
} -ThrottleLimit 10
$rootDict['odd','even']
如果内部哈希表条目在同时更新时是线程安全的,您可以期望两个计数器都为
50 ,但是我在我的笔记本电脑上得到了如下结果:
Name Value
---- -----
Counter 46
Counter 43
我们可以看到,在该过程中,内部“Counter”条目的多个更新被丢失,这可能是由于并发更新所致。
为了测试这个假设,让我们进行同样的实验,但使用另一种并发字典类型代替哈希表:
using namespace System.Collections.Concurrent
$rootDict = [ConcurrentDictionary[string,ConcurrentDictionary[string,int]]]::new()
1..100 |ForEach-Object -Parallel {
$dict = $using:rootDict
$rootKey = $_ % 2 -eq 0 ? 'even' : 'odd'
$innerDict = $dict.GetOrAdd($rootKey, {param($key) return @{}})
Start-Sleep -Milliseconds (Get-Random -Minimum 50 -Maximum 250)
[void]$innerDict.AddOrUpdate('Counter', {param($key) return 1}, {param($key,$value) return $value + 1})
} -ThrottleLimit 10
$rootDict['odd','even']
现在我明白了:
Key Value
--- -----
Counter 50
Counter 50
我有一些自定义的类扩展了HashSet以避免重复。由于System.Collections.Concurrent中没有HashSet类,那么获取类似功能但具备并发性的推荐方法是什么?
我强烈建议你不要直接继承HashSet,而是将HashSet进行包装,并使用ReaderWriterLockSlim来保护你想向用户公开的所有方法。这样,你就可以在不不必要地牺牲读取访问性能的情况下实现线程安全。
这里,以[int]作为示例数据类型:
using namespace System.Collections.Generic
using namespace System.Threading
class ConcurrentSet
{
hidden [ReaderWriterLockSlim]
$_lock
hidden [HashSet[int]]
$_set
ConcurrentSet()
{
$this._set = [HashSet[int]]::new()
$this._lock = [System.Threading.ReaderWriterLockSlim]::new()
}
[bool]
Add([int]$item)
{
$this._lock.EnterWriteLock()
try{
return $this._set.Add($item)
}
finally{
$this._lock.ExitWriteLock()
}
}
[bool]
IsSubsetOf([IEnumerable[int]]$other)
{
$this._lock.EnterReadLock()
try{
return $this._set.IsSubsetOf($other)
}
finally{
$this._lock.ExitReadLock()
}
}
}
您可以通过将一个 HashSet<object>
包装并使用自定义比较器来控制其行为,从而使包装更加灵活。