2

In Powershell, is there a way to increment a variable from multiple threads safely.

I thought the below code using System.Threading.Interlock::Add would work, but the output shows thread mashing is occurring.

# Threads will attempt to increment this number
$testNumber = 0

# Script will increment the test number 10 times
$script =  {
    Param(
        [ref]$testNumber
    )
    1..10 | % {
        [System.Threading.Interlocked]::Add($testNumber, 1) | Out-Null
    }
}

#  Start 10 threads to increment the same number
$threads = @()
1..10 | % {
    $ps = [powershell]::Create()
    $ps.AddScript($script) | Out-Null
    $ps.RunspacePool = $pool
    $ps.AddParameter('testNumber', [ref]$testNumber) | Out-Null
    
    $threads += @{
        ps = $ps
        handle = $ps.BeginInvoke()
    }
}

# Wait for threads to complete
while ($threads | ? {!$_.Handle.IsCompleted }) {
    Start-Sleep -Milliseconds 100
}

# Print the output (should be 100=10*10, but is between 90 and 100)
echo $testNumber
2
  • I quess you need to create a synchronized hash table, e.g.: [hashtable]::Synchronized(@{ TestNumber = 0 }), see e.g.: stackoverflow.com/a/36716964/1701026 Commented Oct 15, 2020 at 9:11
  • 1
    $pool is undefined a variable. A copy-paste error? Commented Oct 15, 2020 at 9:17

1 Answer 1

0

As iRon mentions in the comments, you'll want to use a synchronized hashtable for reading and writing data across the runspace boundary (see inline comments for explanation):

# Threads will attempt to increment this number
$testNumber = 0

# Create synchronized hashtable to act as gatekeeper
$syncHash = [hashtable]::Synchronized(@{ testNumber = $testNumber })

# Create runspace pool with a proxy variable mapping back to $syncHash
$iss = [initialsessionstate]::CreateDefault2()
$iss.Variables.Add(
  [System.Management.Automation.Runspaces.SessionStateVariableEntry]::new('syncHash', $syncHash,'')
)
$pool = [runspacefactory]::CreateRunspacePool($iss)
$pool.Open()

# Script will increment the test number 10 times
$script =  {
    Param(
        [string]$targetValue
    )
    1..10 | % {
        # We no longer need Interlocked.* and hairy [ref]-casts, $syncHash is already thread safe
        $syncHash[$targetValue]++ 
    }
}

#  Start 10 threads to increment the same number
$threads = @()
1..10 | % {
    $ps = [powershell]::Create()
    $ps.AddScript($script) | Out-Null
    $ps.RunspacePool = $pool

    # We're no longer passing a [ref] to the variable in the calling runspace.
    # Instead, we target the syncHash entry by name
    $ps.AddParameter('targetValue', 'testNumber') | Out-Null
    
    $threads += @{
        ps = $ps
        handle = $ps.BeginInvoke()
    }
}

# Wait for threads to complete
while ($threads | ? {!$_.Handle.IsCompleted }) {
    Start-Sleep -Milliseconds 100
}

$errorCount = 0
# End invocation lifecycle
$threads|%{
  if($_.ps.HadErrors){
    $errorCount++
  }
  $_.ps.EndInvoke($_.handle)
}

if($errorCount){
  Write-Warning "${errorCount} thread$(if($errorCount -gt 1){'s'}) had errors"
}

# Et voila, $syncHash['testNumber'] is now 100
$syncHash['testNumber']
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.