最近,我在NetworkEndpoint类中发布了一个关于提高线程和套接字安全性的问题。以前的职位
我已经实施了在我得到的答案中所建议的修改。当我要求第二次检查以确认我的变化时,我被告知发布另一个问题。
下面是我修改的代码以及一些后续问题。要查看全班同学,请参考我之前的帖子。
private object connectedLock = new object();
public bool Connected {
get {
lock (connectedLock) {
if (connection == null) return false;
return connection.Connected;
}
}
}
private object sendLock = new object();
public void Send(NetworkHeader header, byte[] data) {
if (!Connected) throw new InvalidOperationException("NetworkEndpoint must be connected before sending.");
try {
lock (sendLock) {
connection.Send(ByteUtils.Combine(header.Serialize(), data));
}
} catch (SocketException) {
Disconnect();
}
}
private object disconnectLock = new object();
public void Disconnect() {
if (Connected) {
lock (disconnectLock) {
connection?.Shutdown(SocketShutdown.Both);
connection?.Close();
connection = null;
OnDisconnected();
Clear();
}
}
}if (Receiving) return;而多余?private object initializeLock = new object();
public void InitializeReceiveLoop() {
lock (initializeLock) {
if (Receiving) return;
Receiving = true;
}
BeginReceive();
}public void Clear() {
foreach (Delegate d in DataReceived.GetInvocationList())
DataReceived -= (EventHandler<NetworkReceiveEventArgs>)d;
foreach (Delegate d in Disconnected.GetInvocationList())
Disconnected -= (EventHandler)d;
}private void EndReceive(IAsyncResult result) {
byte[] dataBuffer = null;
NetworkHeader header = null;
try {
if (connection.EndReceive(result) > 0) {
header = NetworkHeader.Deserialize(headBuffer);
dataBuffer = new byte[header.DataSize];
int offset = 0;
while (offset < header.DataSize) {
int lengthRead = connection.Receive(dataBuffer, offset,
(int)header.DataSize - offset, SocketFlags.None);
if (lengthRead == 0) {
Disconnect();
return;
}
else offset += lengthRead;
}
}
} catch (SocketException) {
Disconnect();
return;
}
OnDataReceived(header, dataBuffer);
BeginReceive();
}发布于 2019-06-27 20:34:44
可以更简单地清除事件:
公开无效Clear() {DataReceived.GetInvocationList()中代表d) DataReceived -= (EventHandler)d;
public void Clear() {
DataReceived = null;
Disconnected = null;
}这绝不是对代码的全面回顾,但我想向您解释几件事。
您正在使用单独的锁发送、检查连接状态、初始化和断开连接。请考虑使用单独的锁对所有需要公共线程感知的资源Connected的操作所产生的影响。因为它们都采用不同的锁,所以它们可以同时更改状态并破坏另一个正在进行的操作。这就是为什么所有这些操作都应该使用单一锁的原因。
private object syncRoot = new object();但即便如此,由于您在锁定之前检查了条件,数据完整性仍可能被不幸的事件流破坏。
public void Send(NetworkHeader header, byte[] data) {
if (!Connected) // .. // <- outside lock
lock (syncRoot) { // <- inside lock, but condition might no longer be true
// ..
}
}例如,假设两个线程同时调用Send和Disconnect。一种可能的流动是:
- thread A: call Send // thread A is first
- thread A: check IsConnected: true
- thread B: call Disconnect // thread B starts a fraction later
- thread B: check IsConnected: true
- thread B: take lock
- thread B: set IsConnected: false
- thread B: release lock
- thread A: take lock // thread A reaches a point where it
// expected its state to still be valid, but thread B changed it
- thread A: connection.Send(..) // <- null-reference exception以前有一次关于双重检查锁的建议。但是这里有一个很好的理由不去使用它。所以我们得把锁里的条件取下来。
public void Send(NetworkHeader header, byte[] data) {
lock (syncRoot) {
if (!Connected) // ..
// ..
}
}接下来,我们可以将Connected更改为接受易失布尔,因此在获取其值时不需要额外的锁定。只需确保在建立连接并在Disconnect上重置时设置它的值。易失性字段被读取为原子操作。您得到的是实际值,而不是缓存值(这可能是为了在非并发上下文中优化状态)。
private volatile bool _connected = false;
public bool Connected => _connected; // <- no locking required我希望这能解释一些锁和线程安全的基本概念。
https://codereview.stackexchange.com/questions/223087
复制相似问题