线程仍然很新,所以我确信这是一个小问题和一个重复的问题,但我一直无法找到浏览线程的答案。
我在C#中有一个端口扫描应用程序。我使用线程池为每个端口旋转一个新的TcpClient,并探测它是否打开。在经历了闭包和线程同步的概念之后,当多个线程试图将其结果保存到Orchestrator.hosts (List)中的不同索引时,我遇到了一个问题。
我有多个线程试图更新单个列表结果对象。我的理解是,只要我在写时锁定对象,这是很好的,但是我发现在一些更新中,多个条目得到了相同的更新。IE,Thread #1应该将Hosts[0].Ports[0].Status更新为"Open",会发生什么:线程#1使用端口结果更新多个主机,尽管传递了主机的特定索引。Hosts[0].Ports[0].Status改为"Open",Hosts[1].Ports[0].Status改为"Open",Hosts[2].Ports[0].Status改为"Open",
不知道我的问题在哪里。我调用的静态方法来执行给定端口的探测
public static void ScanTCPPorts()
{
// Create a list of portsToScan objects to send to thread workers
//List<ScanPortRequest> portsToScan = new List<ScanPortRequest>();
using (ManualResetEvent resetEvent = new ManualResetEvent(false))
{
int toProcess = 0;
for (var i = 0; i < hostCount; i++) // Starting at Begining
{
int currentHostId = i;
// To hold our current hosts ID (Assign outside of threaded function to avoid race-condition)
if (hosts[i].IsAlive || scanDefinition.isForced())
{
int portCount = hosts[i].Ports.Count;
for (int p = 0; p < portCount; p++)
{
// Thread-safe Increment our workQueue counter
Interlocked.Increment(ref toProcess);
int currentPortPosition = p;
// We need to send the arrayIndex in to the thread function
PortScanRequestResponse portRequestResponse = new PortScanRequestResponse(hosts[currentHostId], currentHostId, hosts[currentHostId].Ports[currentPortPosition], currentPortPosition);
ThreadPool.QueueUserWorkItem(
new WaitCallback(threadedRequestResponseInstance => {
PortScanRequestResponse portToScan = threadedRequestResponseInstance as PortScanRequestResponse;
PortScanRequestResponse threadResult = PortScanner.scanTCPPort(portToScan);
// Lock so Thread-safe update to result
lock (Orchestrator.hosts[portToScan.hostResultIndex])
{
if (threadResult.port.status == PortStatus.Open)
{
// Update result
Orchestrator.hosts[portToScan.hostResultIndex].Ports[portToScan.portResultIndex].status = PortStatus.Open;
//Logger.Log(hosts[currentHostId].IPAddress + " " + hosts[currentHostId].Ports[currentPortPosition].type + " " + hosts[currentHostId].Ports[currentPortPosition].portNumber + " is open");
}
else
{
Orchestrator.hosts[portToScan.hostResultIndex].Ports[portToScan.portResultIndex].status = PortStatus.Closed;
}
// Check if this was the last scan for the given host
if (Orchestrator.hosts[portToScan.hostResultIndex].PortScanComplete != true)
{
if (Orchestrator.hosts[portToScan.hostResultIndex].isCompleted())
{
Orchestrator.hosts[portToScan.hostResultIndex].PortScanComplete = true;
// Logger.Log(hosts[currentHostId].IPAddress + " has completed a port scan");
Orchestrator.hosts[portToScan.hostResultIndex].PrintPortSummery();
}
}
}
// Safely decrement the counter
if (Interlocked.Decrement(ref toProcess) == 0)
resetEvent.Set();
}), portRequestResponse); // Pass in our Port to scan
}
}
}
resetEvent.WaitOne();
}
}下面是单独的公共静态类中的辅助进程。
public static PortScanRequestResponse scanTCPPort(object portScanRequest) {
PortScanRequestResponse portScanResponse = portScanRequest as PortScanRequestResponse;
HostDefinition host = portScanResponse.host;
ScanPort port = portScanResponse.port;
try
{
using (TcpClient threadedClient = new TcpClient())
{
try
{
IAsyncResult result = threadedClient.BeginConnect(host.IPAddress, port.portNumber, null, null);
Boolean success = result.AsyncWaitHandle.WaitOne(Orchestrator.scanDefinition.GetPortTimeout(), false);
if (threadedClient.Client != null)
{
if (success)
{
threadedClient.EndConnect(result);
threadedClient.Close();
portScanResponse.port.status = PortStatus.Open;
return portScanResponse;
}
}
} catch { }
}
}
catch
{ }
portScanResponse.port.status = PortStatus.Closed;
return portScanResponse;
}最初,我从一个空闲变量中提取主机索引,认为这是问题所在,将其移到委托内部。我试着在有写的地方锁定主机对象。我尝试过不同的线程同步技术(CountdownEvent和ManualResetEvent)。
我想我还没有被介绍过一些基本的线程处理原理,或者我犯了一个非常简单的逻辑错误。
发布于 2022-10-29 21:16:26
我有多个线程试图更新单个列表结果对象。我的理解是,只要我在写时锁定对象,就可以了。
我还没有研究过您的代码,但仅上述说法是不正确的。在多线程环境中使用List<T>或任何其他非线程安全对象时,必须同步与对象的所有交互。一次只允许一个线程与对象交互。写入和读取都必须包含在lock语句中,使用相同的锁存对象。即使读取Count也必须是同步的。否则,使用是错误的,并且程序的行为是未定义的。
发布于 2022-10-30 20:51:29
我非常关注这是一个线程问题,因为这是我的第一线程项目。事实证明,我没有意识到List<>对象的副本是对原始对象(引用类型)的引用。我假设我的线程正在以一种不可预测的方式访问我的保存结构,但是我的端口数组都引用了同一个对象。
这是端口的List<>上的“引用类型”与“值类型”问题。
https://stackoverflow.com/questions/74246727
复制相似问题