在PowerShell中,当从多个线程更新单个值时,必须使用锁定机制,例如
Mutex
、
SemaphoreSlim
甚至
Monitor.Enter
,否则更新操作
将不是线程安全的。
同步哈希表不能确保更新键值的线程安全性。
下面是一个简单的演示,证明了上述观点:
$sync = [hashtable]::Synchronized(@{ })
$attempts = 0
do {
$sync['Value'] = 0
$attempts++
0..10 | ForEach-Object -Parallel {
$sync = $using:sync
Start-Sleep -Milliseconds 200
$sync['Value']++
} -ThrottleLimit 11
}
while ($sync['Value'] -eq 11)
"It took $attempts attempts to fail..."
假设我们有一个数组的数组:
$toProcess = 0..10 | ForEach-Object {
, (Get-Random -Count (Get-Random -Minimum 5 -Maximum 10))
}
如果你想要跟踪每个数组中处理过的项目,可以使用
Mutex
来实现:
$processedItems = [hashtable]::Synchronized(@{
Lock = [System.Threading.Mutex]::new()
Counter = 0
})
$toProcess | ForEach-Object -Parallel {
Start-Sleep (Get-Random -Maximum 5)
$ref = $using:processedItems
if($ref['Lock'].WaitOne()) {
$ref['Counter'] += $_.Count
$ref['Lock'].ReleaseMutex()
}
}
$processedCount = ($toProcess | Write-Output | Measure-Object).Count
$processedItems['Counter'] -eq $processedCount
另一个使用
Monitor.Enter
和一个自定义函数来增加计数器的 tread safe 示例,试图模仿 C# 的
lock
语句。
function lock {
param(
[Parameter(Mandatory)]
[object] $Object,
[Parameter(Mandatory)]
[scriptblock] $ScriptBlock
)
try {
[System.Threading.Monitor]::Enter($Object)
& $ScriptBlock
}
finally {
[System.Threading.Monitor]::Exit($Object)
}
}
$utils = [hashtable]::Synchronized(@{
LockFunc = $function:lock.ToString()
Counter = @(0)
})
$toProcess | ForEach-Object -Parallel {
$utils = $using:utils
$function:lock = $utils['LockFunc']
Start-Sleep (Get-Random -Maximum 5)
lock($utils['Counter'].SyncRoot) {
$utils['Counter'][0] += $_.Count
}
}
$processedCount = ($toProcess | Write-Output | Measure-Object).Count
$utils['Counter'][0] -eq $processedCount
在PowerShell中,一个更简单的方法是将并行循环的输出转换为线性循环,在这里您可以安全地更新计数器,而不需要担心线程安全性。
$counter = 0
$toProcess | ForEach-Object -Parallel {
Start-Sleep (Get-Random -Maximum 5)
$_
} | ForEach-Object {
$counter += $_.Count
}
$processedCount = ($toProcess | Write-Output | Measure-Object).Count
$counter -eq $processedCount