在服务器应用程序上,我需要为每个连接的客户端分配一个唯一的ID,所以我这样做:
private short GetFreeID()
{
lock (this.mUsedPlayerIDsSynchronization)
{
for (short I = 1; I < 500; I++)
{
if (ClientIDPool[I] == false)
{
ClientIDPool[I] = true;
return I;
}
}
return -1;
}
}
我的第一个问题:能否更有效地完成,我的意思是表现更好?我在这里读过,我们应该学会编写没有锁的代码。我还在那里阅读了一些原子操作还有其他选择。 第二个问题:如果我想锁定整个课程以便不允许在其中进行任何更改,该怎么办?例如:一个客户端将更新第二个客户端数据,我可以锁定它被绝对阻止的整个第二个客户端类吗?我仍然认为“锁定”只会确保其片段中的代码当时只被一个线程输入,所以我不知道“lock(client2)”是否会导致该类中的任何内容都无法更改,直到此锁定为止释放。
答案 0 :(得分:11)
锁定通常是最简单的方式,这是非常重要的。通常情况下,如果有更有效的处理方式并不重要,只要你有清晰的代码并且足够。
然而,更高效的方法是生成随机GUID,或者如果 想要重用ID,则拥有“池”(例如LinkedList
)未使用的ID。然后,您可以非常快速地从池中取出,并在完成后将ID返回池中(再次快速)。
或者,如果你真的只需要一个整数并且它不 是一个低整数,你可以有一个静态变量,它从0开始,你只是每次递增 - 你如果您愿意,可以使用Interlocked.Increment
进行锁定。我怀疑你会用完64位整数,例如:)
关于你的第二个问题:是的,锁是建议性的。如果类中的所有内容在更改任何字段之前取出相同的锁(并且字段是私有的)那么这会阻止其他代码行为不端......但是每个代码 需要取出锁
编辑:如果你真的只需要一个整数,我仍然建议你只使用Interlocked.Increment
- 即使你的流量增加了1000倍,你也可以使用64位整数。但是,如果您想重用ID,那么我建议创建一个新类型来表示“池”。给 计算已创建的数量的计数器,这样如果你用完就可以分配一个新项目。然后只需将可用的内容存储在Queue<int>
,LinkedList<int>
或Stack<int>
中(您使用的内容并不重要)。假设您可以信任自己的代码以合理地返回ID,那么您可以使API简单如下:
int AllocateID()
void ReturnID(int id)
AllocateID
将检查池是否为空,如果是,则分配新ID。否则,它只会删除池中的第一个条目并返回该条目。 ReturnID
只会将指定的ID添加到池中。
答案 1 :(得分:2)
您在扫描数组时锁定。
你最好有2个筹码。一个是带有免费ID的,一个是带有ID的。这样你就可以弹出第一个堆叠中的一个并将其推到第二个堆栈上。
这样你锁定的时间就会长得多。
答案 2 :(得分:2)
You can allocate state on thread local memory.线程本地内存是线程安全的(只要你没有传递指针arround)。
您可以使用两个整数生成唯一编号,只有一个是同步编号。
整数1:一个递增的整数,代表线程,每次初始化一个线程时都会产生一个新的数字(这应该是一个罕见的事件)。
Integer2:在线程初始化时,您将在0开始此整数。
您将使用存储在线程本地存储器中的两个整数作为唯一整数,并且整数2将正常递增(解锁)。
这样生成唯一整数绝对是线程安全的 - 即,您不必使用原子CPU指令--Interlocked.increment(这会导致硬件级性能损失)。
- 编辑:缓存一致性 -
缓存一致性
减少内存访问所需的时间不同的缓存 使用:最近访问的内存 复制在CPU缓存中 明显快于普通人 记忆。未来访问相同 地址将使用保存在缓存中的数据, 减少获取时间。问题 出现在SMP中(对称的 多处理系统,在那里 几个处理器都有自己的缓存 内存:当一个处理器发生变化时 内存区域中的变量,由...使用 它同时是几个处理器 实际上改变了自己的副本 变量,位于缓存中,而 共享变量仍然具有原始性 值。这个问题不可能 通过在a上使用volatile关键字解决 共享变量,因为这只会 保证写入内存 指示将出现在结果中 程序,但与之相关的操作 缓存仍未指定。的 当然,可以禁用CPU 缓存,将内存映射为无缓存 (PAGE_NOCACHE保护标志 VirtualAlloc()Win32 API函数), 但同时显着放缓 这有一些限制:因为 例如,互锁指令可以 在无缓存上引发硬件异常 存储器中。
为了正确处理SMP系统中存储在更多缓存中的数据 比一个处理器应该是相同的 在所有缓存中。这意味着CPU 缓存必须同步(保持 连贯的)硬件级别**。但它 重要的是要注意缓存 同步(缓存流程 一致性交通) 与程序执行异步:** 当一个CPU更改共享的值时 暂时变量另一个CPU 观察旧价值。 这意味着CPU 继续执行而不等待 用于缓存一致性操作 完成。此外,如果两个 变量(a然后b)被改变了 第一个CPU,另一个CPU可以观察到 b已经改变了早于。
互锁指令在此方面存在很大差异 点。完全,互锁 指令是一个命令 直接在物理上的东西 锁定总线下的内存。这意味着 缓存不一致的情况并非如此 影响共享的程序 仅使用访问变量 互锁指令(注意 两个过程,谁读的 变量和写入它应该 使用互锁指令)。
- 编辑:进一步澄清: -
根据您当前的设计,互锁增量为indeed your best bet,但远非理想。您的CPU有一个非常快的缓存片(通常以与CPU相同的速度计时)。如果你的线程有本地内存,它将被拉入你的线程所在的CPU,这意味着你的CPU不必转到主内存,它可以全速飞行。
如果使用互锁增量,则CPU必须
你可以不用这个。我可能看起来很迂腐,因为开销只有be a 100% decrease in relative performance。然而,在具有4个物理CPU和16个核心的工业服务器应用程序中,每个请求都会触发此UID生成器...相信你,你的总线将被搞砸。微优化是编程中的一个重要领域,特别是当我们现在横向扩展时。
答案 3 :(得分:1)
我建议您使用GUID,除非您需要使用短片。 Guid.NewGuid()是线程安全的,因此您无需使用锁或其他同步机制。
答案 4 :(得分:1)
你关心返回的ID吗?您可以每次使用Interlocked.Increment递增客户端ID或生成GUID(前者可能更快)。
然后使用一个简单的计数器来跟踪连接的客户端数量,而不是每次都扫描数组。