In this question, it was explained how to add to a concurrent ThreadSafe collection Powershell: How to add Result to an Array (ForEach-Object -Parallel)
I have a simpler use case , where I would just like to increment a single value. (Integer).
Is it possible to do in Powershell using some sort of Atomic Integer data type?
$myAtomicCounter = 0
$myItems | ForEach-Object -Parallel {
#...other work
$myAtomicCounter.ThreadSafeAdd(2)
# .. some more work using counter
}
Write-Host($myAtomicCounter)
CodePudding user response:
In PowerShell when updating a single value from multiple threads you must use a locking mechanism, for example Mutex
, SemaphoreSlim
or even Monitor.Enter
otherwise the updating operation will not be thread safe.
For example, supposing you have an array of arrays:
$toProcess = 0..10 | ForEach-Object {
, (Get-Random -Count (Get-Random -Minimum 5 -Maximum 10))
}
And you wanted to keep track of the processed items in each array, here is how you could do it using Mutex
:
$processedItems = [hashtable]::Synchronized(@{
Lock = [System.Threading.Mutex]::new()
Counter = 0
})
$toProcess | ForEach-Object -Parallel {
# using sleep as to emulate doing something here
Start-Sleep (Get-Random -Maximum 5)
# bring the local variable to this scope
$ref = $using:processedItems
# lock this thread until I can write
if($ref['Lock'].WaitOne()) {
# when I can write, update the value
$ref['Counter'] = $_.Count
# and realease this lock so others threads can write
$ref['Lock'].ReleaseMutex()
}
}
$processedCount = ($toProcess | Write-Output | Measure-Object).Count
# Should be True:
$processedItems['Counter'] -eq $processedCount
A much simpler approach in PowerShell would be to output from your parallel loop into a linear loop where you can safely update the counter without having to care about thread safety:
$counter = 0
$toProcess | ForEach-Object -Parallel {
# using sleep as to emulate doing something here
Start-Sleep (Get-Random -Maximum 5)
# when this thread is done,
# output this array of processed items
$_
} | ForEach-Object {
# then the output from the parallel loop is received in this linear
# thread safe loop where we can update the counter
$counter = $_.Count
}
$processedCount = ($toProcess | Write-Output | Measure-Object).Count
# Should be True:
$counter -eq $processedCount