我目前有一个包含4个线程的程序。
4个线程是“工作线程”,每个线程都有一个监视专用设备的专用串行端口。因此,工作线程1监视Com端口1,线程2监视Com端口2等。
这一切都很好。没有冲突。
但是,4个工作线程都必须向第5个Comm端口发送命令,这是指向可以为其他设备供电的设备的通信链接。
I.E.他们都必须共享一个特定的资源,第五个com端口。
当他们向第5个共享发送命令时,每个线程必须等到命令完成后再继续。
我遵循Dan的编码示例(谢谢!)并尝试形成原型测试代码。 这个SEEMS可以工作。
我很感激对代码的严格审查,看看我是否正朝着正确的方向前进。
道歉,如果我之前没有像以前那样使用线程那么解释得那么好。处理共享资源对我来说是新的。此外,我只是想了解Stackoverflow的工作原理!!
非常感谢
答案 0 :(得分:1)
使用资源和锁的共享实例的简化解决方案。
Public Class Resource
Public Function Read() As String
Return "result"
End Function
End Class
Public Class ResourceUser
Private Shared resourceLock As New Object
Private Shared r As New Resource()
Public Function Read()
Dim res As String
SyncLock resourceLock
res = r.Read()
End SyncLock
Return res
End Function
End Class
使用示例:
Sub Main()
Dim t1 As New Threading.Thread(AddressOf DoSomethingWithResourceUser)
Dim t2 As New Threading.Thread(AddressOf DoSomethingWithResourceUser)
t1.Start()
t2.Start()
End Sub
Private Sub DoSomethingWithResourceUser()
Dim ru As New ResourceUser()
ru.Read()
End Sub
答案 1 :(得分:1)
这是另一个更具体的串行通信示例。它使用字典来跟踪物理通信资源及其相关锁,以便您可以对不同的通信端口进行异步访问,但同步访问每个通信端口。
Sub Main()
Dim c1 As New CommPortThreadSafe("COM1")
Dim c2 As New CommPortThreadSafe("COM2")
Dim c3 As New CommPortThreadSafe("COM1")
Dim t1 As New Threading.Thread(Sub() c1.Read())
Dim t2 As New Threading.Thread(Sub() c2.Read())
Dim t3 As New Threading.Thread(Sub() c3.Read())
' t1 and t3 can't be in critical region at same time
' t2 will be able to run through critical region
t1.Start()
t2.Start()
t3.Start()
End Sub
Public Class CommPort
Public Property Name As String
Public Function Read() As String
Return "result"
End Function
End Class
Public Class CommPortThreadSafe
Private Shared resourceLocks As New Dictionary(Of String, Object)()
Private Shared comms As New Dictionary(Of String, CommPort)()
Private Shared collectionLock As New Object()
Private commPortName As String
' constructor takes the comm port name
' so the appropriate dictionaries can be set up
Public Sub New(commPortName As String)
SyncLock collectionLock
Me.commPortName = commPortName
If Not comms.ContainsKey(commPortName) Then
Dim c As New CommPort()
Dim o As New Object()
c.Name = commPortName
' configure comm port further etc.
comms.Add(commPortName, c)
resourceLocks.Add(commPortName, o)
End If
End SyncLock
End Sub
Public Function Read()
Dim res As String
SyncLock resourceLocks(Me.commPortName)
res = comms(Me.commPortName).Read()
End SyncLock
Return res
End Function
End Class
解决您最近的修改:
线程A将以相同的方式声明通信端口。实际上,这是这种模式的一个好处(类似于多重模式),当只使用一个通信端口时,它就像一个单独的模式。此代码可用于所有线程:
Dim myCommPort As New CommPortThreadSafe("COM1")
读取内部的锁定将同步对COM1的访问,因为&#34; COM1&#34; (通信端口的名称)实际上是用于锁定的Dictionary<string, object>
的关键。因此,当任何线程到达此代码时,使用相同的键进行键控时,该区域只能由单个线程访问,因为它们都使用相同的键。
SyncLock resourceLocks(Me.commPortName)
res = comms(Me.commPortName).Read()
End SyncLock
如您所见,该字符串是在构造函数中设置的,因此只要所有线程创建的对象将相同的字符串传递给构造函数,它们都将具有对同一CommPort
的基础间接引用。如果名称在其字典中已经存在,则构造函数只能创建实例:
If Not comms.ContainsKey(commPortName) Then
Dim c As New CommPort()
这是另一个只使用一个通信端口的示例用法:
Sub Main()
Dim ts As New ThreadStart(
Sub()
Dim c As New CommPortThreadSafe("COM1")
For i As Integer = 0 To 99
c.Read()
Next
End Sub)
Dim t1 As New Threading.Thread(ts)
Dim t2 As New Threading.Thread(ts)
Dim t3 As New Threading.Thread(ts)
Dim t4 As New Threading.Thread(ts)
t1.Start()
t2.Start()
t3.Start()
t4.Start()
End Sub
在这个例子中,我们启动4个线程,每个线程执行threadstart中的代码。有一个循环读取通讯端口。如果您对此进行测试,只要整个读取发生在Read()
内,您将看到它是线程安全的,当然您需要开发它。您可能有另一个图层,您在其中发送自定义命令并等待响应。这两个操作都应该位于每个自定义函数中的单个SyncLock中。如果线程B执行类似的操作,它应该使用相同的类。
答案 2 :(得分:0)
表格测试代码
Imports System.Threading
Public Class Form1
Private Worker(4) As jWorker '4 Worker Object
Public myWorkerThread(4) As Threading.Thread '4 runtime threads
Private Checker As jChecker '1 Checking Object
Public myCheckerThread As Threading.Thread ' Thread to check status of Resource
Dim MainThreadResouce As jResourceUser
'Assume the actual serial port is opened here
'so its available for access by the jResource Object.
'Pressing button 1 will start up all the threads
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'setup a another ResourceUser object here so can see one of the shared values from the form.
MainThreadResouce = New jResourceUser
'Setup and start worker threads - basically these do work and regularly
'send commands to a shared resource
For I = 1 To 4
Worker(I) = New jWorker
Worker(I).id = I
myWorkerThread(I) = New Threading.Thread(AddressOf Worker(I).doWork)
myWorkerThread(I).Start()
Next I
'Start Checking thread - regularly checks something in the resource
'please ignore this for now!
Checker = New jChecker
myCheckerThread = New Threading.Thread(AddressOf Checker.dochecking)
myCheckerThread.Start()
End Sub
End Class
======================== 工人线,模拟监测线
Imports System.Threading
Public Class jWorker
Private _id As Integer = 0
Private _workCount As Integer = 0
Private resourceUser As jResourceUser
Public workerStatus As String = ""
Public Property id() As Integer
Get
Return _id
End Get
Set(ByVal Value As Integer)
Me._id = Value
End Set
End Property
Public ReadOnly Property count() As Integer
Get
Return _workCount
End Get
End Property
Public Sub New()
resourceUser = New jResourceUser
End Sub
Sub doWork()
workerStatus = "Started"
Do Until False
Thread.Sleep(1000)
_workCount += 1
If _workCount Mod 5 = 0 Then
'THe line below would cause a bottleneck
'As it prevents the system trying to powercycle any other
'modems whilst its doing just one.
'resourceUser.PowerCycle(_id)
Debug.Print("W" & _id & ", Worker - Requesting Power OFF +++++++++++++")
resourceUser.Poweroff(_id)
Debug.Print("W" & _id & ", Worker - Waiting 10 secs for modem to settle")
Thread.Sleep(10000)
Debug.Print("W" & _id & ", Worker - Requesting Power ON")
resourceUser.PowerOn(_id)
Debug.Print("W" & _id & ", Worker - Finished Power cycle ------------")
End If
Loop
End Sub
End Class
=================== 资源用户 - 控制对共享资源的访问
Public Class jResourceUser
'Variables for handling resouce locking
Private Shared resourceLock As New Object
Private Shared _resource As New jResource("Com1", "ON")
'keeps a status of which workers have signalled an OFF or ON via the Resource
'in the form of a string 11213141 (device number (1-4) - 1 for on, 0 for off
Private Shared _powerStatus As String
Public ReadOnly Property PowerStatus As String
Get
Return _powerStatus
End Get
End Property
Public Sub New()
_powerStatus = _resource.PowerState
End Sub
Sub PowerOn(ByVal WorkerID As Integer)
Debug.Print("W" & WorkerID & ", ResouceUser - requesting Lock for Power ON [" & _powerStatus & "]")
SyncLock resourceLock
_resource.TurnOn(WorkerID)
_powerStatus = _resource.PowerState
Debug.Print("W" & WorkerID & ", ResouceUser - Turned On, Device statuses " & _powerStatus & "]")
End SyncLock
End Sub
Sub Poweroff(ByVal WorkerID As Integer)
Debug.Print("W" & WorkerID & ", ResouceUser requesting Lock for Power OFF [" & _powerStatus & "]")
SyncLock resourceLock
_resource.TurnOff(WorkerID)
_powerStatus = _resource.PowerState
Debug.Print("W" & WorkerID & ", ResouceUser - Turned Off, Device statuses [" & _powerStatus & "]")
End SyncLock
End Sub
'Not going to work as it blocks the whole system when it could be
'reseting other modems.
Sub PowerCycle(ByVal WorkerID As Integer)
SyncLock resourceLock
Debug.Print("W" & WorkerID & ", ResourceUser - Requesting Power CYcle LockPower")
_resource.PowerCycle(WorkerID)
_powerStatus = _resource.PowerState
Debug.Print("W" & WorkerID & ", ResourceUser - Power Cycled")
End SyncLock
End Sub
Function CheckState() As String
SyncLock resourceLock
Return _resource.CheckState
_powerStatus = _resource.PowerState
End SyncLock
End Function
End Class
============
资源 - 关于共享资源的实际工作
'THis code would directly handle interactions
'with one specific com port that has already
'been configured and opened on the main thread.
Public Class jResource
Private _ComPort As String
Private _state As String
Private _PowerState As String
Private _CheckState As String
'Record the com port used for this resource
Public Property ComPort() As String
Get
Return _ComPort
End Get
Set(ByVal Value As String)
Me._ComPort = Value
End Set
End Property
'Returns the a particular status of the resouce
Public ReadOnly Property CheckState As String
Get
'here I'd send a few command to the comm port
'pick u the response and return it
Return _state
End Get
End Property
'The connected serial port is used to power cycle serveral devices
'this property returns the state of all those devices.
Public ReadOnly Property PowerState() As String
Get
Return _PowerState
End Get
End Property
Public Sub New(ByVal name, ByRef state)
Me._ComPort = name
Me._state = state
Me._PowerState = "11213141"
Me._CheckState = "ON"
End Sub
'Simulate a off command sent by a worker
'via its resourceUser object
Public Sub TurnOn(ByVal intWorker As Integer)
'simulate some work with the com port
Dim myTimeOut As DateTime
myTimeOut = Now.AddMilliseconds(500)
Do Until Now > myTimeOut
Loop
'Set the status to show that Device is on.
_PowerState = _PowerState.Replace(intWorker & "0", intWorker & "1")
Debug.Print("W" & intWorker & ", Resource - issued TurnON, Device Statuses [" & _PowerState & "]")
End Sub
Public Sub TurnOff(ByVal intWorker As Integer)
'simulate some work
Dim myTimeOut As DateTime
myTimeOut = Now.AddMilliseconds(500)
Do Until Now > myTimeOut
Loop
'Here would send command to Com port
'Set the status to show that Device is Off.
_PowerState = _PowerState.Replace(intWorker & "1", intWorker & "0")
Debug.Print("W" & intWorker & ", Resource - issued TurnOFF, Device Statuses [" & _PowerState & "]")
End Sub
Public Sub PowerCycle(ByVal intWorker As Integer)
Debug.Print("W" & intWorker & ", Resource - issued PowerCycle, Device Statuses [" & _PowerState & "]")
'Here would send command to Com port
'Set the status to show that Device is Off.
_PowerState = _PowerState.Replace(intWorker & "1", intWorker & "0")
Debug.Print("W" & intWorker & ", Resource - issued TurnOFF, Device Statuses [" & _PowerState & "]")
'simulate some work - takes a while for device to turn off
Dim myTimeOut As DateTime
myTimeOut = Now.AddMilliseconds(10000)
Do Until Now > myTimeOut
Loop
'Here would send command to Com port
'Set the status to show that Device is Off.
_PowerState = _PowerState.Replace(intWorker & "1", intWorker & "1")
Debug.Print("W" & intWorker & ", Resource - issued TurnON, Device Statuses [" & _PowerState & "]")
'simulate some work
myTimeOut = Now.AddMilliseconds(10000)
Do Until Now > myTimeOut
Loop
'Here would send command to Com port
End Sub
End Class