作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!


我写了如下代码:

public sealed class Route
{
    [ThreadStatic]
    public static Int64 MaxConcurrentRequestPerCore = 0;

    public static async Task CallbackAsync(HttpContext ctx)
    {
        Interlocked.Increment(ref MaxConcurrentRequestPerCore);
        // other code
    }
}

原子操作的位置抛出异常:

System.DataMisalignedException: A datatype misalignment was detected in a load or store instruction.
   at System.Threading.Interlocked.Increment(Int64\u0026 location)
   at route.Route.CallbackAsync(HttpContext ctx) in /Users/ahfu/code/github.com/ahfuzhang/flutter_admin_site/server/csharp_backend/src/route/route.cs:line 33
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication\u00601 application)

Claude 对错误原因进行了分析:

原因分析
[ThreadStatic] + Interlocked.Increment 组合是错误的。

具体原因
[ThreadStatic] 字段不保证内存对齐。Interlocked.Increment(ref Int64) 在 x86/x64 上要求操作数必须是 8 字节对齐的内存地址,否则会抛出 DataMisalignedException。

[ThreadStatic] 的内存布局由运行时管理,线程局部存储(TLS)块中的字段排列不像普通静态字段那样有对齐保证,Int64 字段可能落在非 8 字节对齐的地址上。

逻辑矛盾
除了对齐问题,这里还有一个语义上的矛盾:

[ThreadStatic] 的语义是"每个线程独立一份",每个线程看到的是自己的计数器
Interlocked 的语义是"跨线程原子操作"
两者组合没有意义——既然每个线程各自独立,根本不需要原子操作;而要实现"全局并发请求数"的过载保护,就不该用 [ThreadStatic]

我原本是想在 threadlocal 的基础上加上原子操作,作为双保险。因为我担心 TaskA 中认为是 thread 1,而 TaskA 被调度到 thread 2 后,会引用 thread 1 中的 TLS.

结论:

  • 我的做法确实多余了
  • 但是不应该抛出异常
    • 抛出异常意味着,在 thread local 上分配的值类型变量,不是按照 8 字节对齐的 => 如果在 thread local 变量上频繁操作,可能带来性能问题。
    • 这实在是太坑了。起码原生的整数类型应该是 8 字节对齐的。

已经给官方提了一个 issue:
https://github.com/dotnet/runtime/issues/129733
[ThreadStatic] Int64 field may be misaligned and causes Interlocked.Increment to throw DataMisalignedException