Stateful Goroutines in Visual Basic .NET

Our example demonstrates how to manage state using threads and message passing in Visual Basic .NET. This approach aligns with the idea of sharing memory by communicating and having each piece of data owned by exactly one thread.

Imports System
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks

Module StatefulThreads
    ' These classes encapsulate read and write operations
    Class ReadOp
        Public Key As Integer
        Public Result As TaskCompletionSource(Of Integer)
    End Class

    Class WriteOp
        Public Key As Integer
        Public Val As Integer
        Public Result As TaskCompletionSource(Of Boolean)
    End Class

    Sub Main()
        Dim readOps As Long = 0
        Dim writeOps As Long = 0

        ' Channels for read and write requests
        Dim reads As New BlockingCollection(Of ReadOp)()
        Dim writes As New BlockingCollection(Of WriteOp)()

        ' This thread owns the state
        Dim stateThread = New Thread(Sub()
            Dim state As New Dictionary(Of Integer, Integer)()
            While True
                Dim readOp As ReadOp = Nothing
                Dim writeOp As WriteOp = Nothing

                If reads.TryTake(readOp, 0) Then
                    If state.ContainsKey(readOp.Key) Then
                        readOp.Result.SetResult(state(readOp.Key))
                    Else
                        readOp.Result.SetResult(0)
                    End If
                ElseIf writes.TryTake(writeOp, 0) Then
                    state(writeOp.Key) = writeOp.Val
                    writeOp.Result.SetResult(True)
                Else
                    Thread.Sleep(1)
                End If
            End While
        End Sub)
        stateThread.Start()

        ' Start 100 read threads
        For r = 0 To 99
            Dim t = New Thread(Sub()
                Dim rnd As New Random()
                While True
                    Dim read As New ReadOp With {
                        .Key = rnd.Next(5),
                        .Result = New TaskCompletionSource(Of Integer)()
                    }
                    reads.Add(read)
                    read.Result.Task.Wait()
                    Interlocked.Increment(readOps)
                    Thread.Sleep(1)
                End While
            End Sub)
            t.Start()
        Next

        ' Start 10 write threads
        For w = 0 To 9
            Dim t = New Thread(Sub()
                Dim rnd As New Random()
                While True
                    Dim write As New WriteOp With {
                        .Key = rnd.Next(5),
                        .Val = rnd.Next(100),
                        .Result = New TaskCompletionSource(Of Boolean)()
                    }
                    writes.Add(write)
                    write.Result.Task.Wait()
                    Interlocked.Increment(writeOps)
                    Thread.Sleep(1)
                End While
            End Sub)
            t.Start()
        Next

        ' Let the threads work for a second
        Thread.Sleep(1000)

        ' Report the operation counts
        Console.WriteLine($"readOps: {readOps}")
        Console.WriteLine($"writeOps: {writeOps}")
    End Sub
End Module

In this example, we use a dedicated thread to manage the state, which is a dictionary. Other threads send read and write requests to this state-owning thread via BlockingCollection(Of T), which serves as our channel equivalent.

We start 100 read threads and 10 write threads. Each thread continuously sends requests to the state-owning thread and waits for the response.

The state-owning thread processes read and write requests as they come in, ensuring that the state is accessed in a thread-safe manner.

We use Interlocked.Increment to safely count the number of operations performed.

After letting the threads run for a second, we print out the total number of read and write operations performed.

Running this program might show output similar to this:

readOps: 71708
writeOps: 7177

This thread-based approach in Visual Basic .NET is more involved than using locks (the equivalent of mutexes in Go). However, it can be useful in certain scenarios, especially when dealing with complex synchronization requirements or when you want to explicitly control the flow of data between threads.

Choose the approach that feels most natural and makes it easiest to reason about the correctness of your program. In Visual Basic .NET, you have various synchronization primitives at your disposal, including locks, events, and concurrent collections, which you can use based on your specific needs.