{ "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "线程本地存储\n", "============================== " ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "线程本地存储(Thread Local Storge,简称TLS)机制用于实现按线程隔离的线程本地变量,对于同一个线程本地变量,各个线程分别有独立的值,修改的值只能修改的线程可见。
\n", "\n", "线程要地存储可以分为原生实现与托管实现,原生实现指的是调用操作系统提供的接口访问原生线程对应的线程本地存储,而托管实现指的是调用.net提供的接口访问托管线程对应的线程本地存储。
\n", "\n", "原生实现,在Windows和linux上的实现不同,但同样都是调用系统提供的接口。
\n", "\n", "注意:线程本地存储目的是每线程复制一份数据,做到线程独立与隔离,而不是使用共享数据与同步(使用与同步实现在原子操作、锁、信号量等方面)。" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## 全局设置,语言设置、Nuget包引用、空间引用等" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "//全局设置\n", "#!csharp\n", "using System.Threading;\n", "using System.Threading.Channels;\n", "using System.Threading.Tasks;\n", "\n", "//全局变量\n", "var noteBookThreadDesc = \"NoteBook线程\";\n", "\n", "//全局方法\n", "//显示线程信息\n", "public void ShowThreadInfo(Thread showThread=null, string describe = null)\n", "{\n", " if(showThread == null)\n", " {\n", " showThread = Thread.CurrentThread;\n", " }\n", "\n", " if(string.IsNullOrWhiteSpace(describe))\n", " {\n", " describe = showThread.Name == null ? \"无名\" : showThread.Name;\n", " }\n", "\n", " Console.WriteLine($\"{describe}线程ID:{showThread.ManagedThreadId} \");\n", " Console.WriteLine($\"{describe}线程名:{showThread.Name} \");\n", " Console.WriteLine($\"{describe}线程状态:{showThread.ThreadState} \");\n", " Console.WriteLine($\"{describe}线程模式:{showThread.GetApartmentState()} \");\n", " Console.WriteLine($\"{describe}激活:{(showThread.IsAlive ? \"活动\" : \"非活动\")} \");\n", " Console.WriteLine($\"{describe}线程池线程:{(showThread.IsThreadPoolThread ? \"是的\" : \"否\")} \");\n", " Console.WriteLine($\"{describe}后台线:{(showThread.IsBackground ? \"是的\" : \"不是\")} \");\n", " Console.WriteLine($\"{describe}区域:{showThread.CurrentCulture}\");\n", " Console.WriteLine($\"{describe}UI区域:{showThread.CurrentUICulture}\");\n", " Console.WriteLine($\"{describe}优先级:{showThread.Priority}\");\n", "}\n", "\n", "//显示线程状态\n", "public void ShowThreadState(Thread showThread=null, string describe = null)\n", "{\n", " if(showThread == null)\n", " {\n", " showThread = Thread.CurrentThread;\n", " }\n", "\n", " if(string.IsNullOrWhiteSpace(describe))\n", " {\n", " describe = showThread.Name == null ? \"无名\" : showThread.Name;\n", " }\n", " Console.WriteLine($\"{describe}线程状态:{showThread.ThreadState} \");\n", "}" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## 非线程线程安全 写法" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "//非线程安全类\n", "public class Computer\n", "{\n", " //循环总次数\n", " public static int TotalLoopNumber = 0;\n", "\n", " //方法循环次数\n", " public readonly int LoopNumber = 100;\n", "\n", " //循环业务\n", " public void SetTotalLoopNumber()\n", " {\n", " for (int i = 1; i <= LoopNumber; i++)\n", " {\n", " //关键点:这里的 += ++ -- -= 之类不是原子操作,相当于 先\"运算\"再\"赋值\"\n", " TotalLoopNumber += 1;\n", " \n", " Thread.Sleep(10);\n", " }\n", " Console.WriteLine($\"线程[{Thread.CurrentThread.ManagedThreadId.ToString(\"000\")}] 执行了 {LoopNumber} 次,结束时 TotalLoopNumber = {TotalLoopNumber}\");\n", " }\n", "}\n", "\n", "//使用{}实现:使用域隔离和代码折叠\n", "{\n", " // 清空上次执行共享数据\n", " Computer.TotalLoopNumber = 0;\n", "\n", " // 实例对你\n", " var demo = new Computer();\n", " var threads = new List()\n", " {\n", " new Thread(()=>demo.SetTotalLoopNumber()){Name=\"thread_a\"},\n", " new Thread(()=>demo.SetTotalLoopNumber()){Name=\"thread_b\"}\n", " };\n", "\n", " threads.ForEach(t => t.Start());\n", " threads.ForEach(t => t.Join());\n", "\n", " //主线程(内核)调用类的静态字段\n", " Console.WriteLine($\"主(执行)线程[{Thread.CurrentThread.ManagedThreadId.ToString(\"000\")}] 结束时,TotalLoopNumber = {Computer.TotalLoopNumber}\");\n", "}\n", "\n", "//因为非线程安全,多执行几次,会出现共享变量 TotalLoopNumber 小于所有线程执行循环的总次数\n", "//但主(调用内核)线程,访问的共享变量值和其它线程改变的值是一样的" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## ThreadStatic Attribute 特性 实现" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "在 .NET 中标记了 [ThreadStatic] 特性的全局变量就是托管线程本地变量。
\n", "\n", "在 .NET 中,每个托管对象都关联一个TLB(Thread Local Block)表,TLB表以 AppDomain ID 为索引保存 TLM(Thread Local Module)表,TLM表以模块ID为索引保存托管线程本地存储空间的开始地址。当 .NET 运行时加载一个程序集时,会枚举程序集中的模块与模块中的全局变量,然后按是否线程本地变量分成两部分,非线程本地变量会保存在 AppDomain 对应的高频堆中;而线程本地变量只计算偏移值,存储空间会在首次访问时分配。 " ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "注意:ThreadStatic 特性,只支持静态字段,不支持实例字段(实例字段不启作用)。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "//使用 ThreadStatic 特性,实现线程本地存储\n", "public class Computer2\n", "{\n", " //循环总次数\n", " [ThreadStatic]\n", " public static int TotalLoopNumber = 0;\n", "\n", " //方法循环次数\n", " public readonly int LoopNumber = 100;\n", "\n", " //循环业务\n", " public void SetTotalLoopNumber()\n", " {\n", " for (int i = 1; i <= LoopNumber; i++)\n", " {\n", " //关键点:这里的 += 不是原子操作,相当于 先\"加\"再\"赋值\"\n", " TotalLoopNumber += 1;\n", " \n", " Thread.Sleep(1);\n", " }\n", " Console.WriteLine($\"线程[{Thread.CurrentThread.ManagedThreadId.ToString(\"000\")}] 执行了 {LoopNumber} 次,结束时 TotalLoopNumber = {TotalLoopNumber}\");\n", " }\n", "}\n", "{\n", " var computer = new Computer2();\n", " var threads = new List()\n", " {\n", " new Thread(()=>computer.SetTotalLoopNumber()){ Name = \"thread_a\" },\n", " new Thread(()=>computer.SetTotalLoopNumber()){ Name = \"thread_b\" }\n", " };\n", "\n", " threads.ForEach(t => t.Start());\n", " threads.ForEach(t => t.Join());\n", "\n", " //主线程(内核)调用类的静态字段\n", " Console.WriteLine($\"主(执行)线程[{Thread.CurrentThread.ManagedThreadId.ToString(\"000\")}] 结束时,TotalLoopNumber = {Computer2.TotalLoopNumber}\");\n", "}\n", "\n", "//使用线程本地存储,共享变量复制到各线程,相互独立、互不影响。\n", "//每线程执行后,共享变量的值相同,主线程没有单独执行方法,所以共享变量值总是0" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## 数据槽 实现" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "//使用 数据槽,实现线程本地存储\n", "public class Computer3\n", "{\n", " //循环总次数\n", " public static int TotalLoopNumber = 0;\n", "\n", " //方法循环次数\n", " public readonly int LoopNumber = 100;\n", "\n", " //数据槽\n", " public LocalDataStoreSlot storeSlot = Thread.AllocateDataSlot(); \n", "\n", " //循环业务\n", " public void SetTotalLoopNumber()\n", " {\n", " for (int i = 1; i <= LoopNumber; i++)\n", " {\n", " Thread.SetData(storeSlot,i);\n", " \n", " Thread.Sleep(1);\n", " }\n", " Console.WriteLine($\"线程[{Thread.CurrentThread.ManagedThreadId.ToString(\"000\")}] 执行了 {LoopNumber} 次,结束时 数据槽 = {Thread.GetData(storeSlot)}\");\n", " }\n", "}\n", "{\n", " var computer = new Computer3();\n", " var threads = new List()\n", " {\n", " new Thread(()=>computer.SetTotalLoopNumber()){ Name = \"thread_a\" },\n", " new Thread(()=>computer.SetTotalLoopNumber()){ Name = \"thread_b\" }\n", " };\n", "\n", " threads.ForEach(t => t.Start());\n", " threads.ForEach(t => t.Join());\n", "\n", " Console.WriteLine($\"因为在线程中使用了数据槽,主线程没有设置过,为Null\");\n", "}\n", "\n", "//使用线程本地存储,共享变量复制到各线程,相互独立、互不影响。\n", "//每线程执行后,共享变量的值相同,主线程没有单独执行方法,所以共享变量值总是0" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## ThreadLocal 泛型类 实现" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "注意: \n", "+ 可以用于静态和实例共享对象 \n", "+ ThreadLocal的值是延迟计算的:每线程第一次调用时计算实际的值" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "//使用 ThreadLocal 泛型类,实现线程本地存储\n", "public class Computer4\n", "{\n", " //循环总次数\n", " public ThreadLocal TotalLoopNumber = new ThreadLocal();\n", "\n", " //方法循环次数\n", " public readonly int LoopNumber = 100;\n", "\n", " //循环业务\n", " public void SetTotalLoopNumber()\n", " {\n", " for (int i = 1; i <= LoopNumber; i++)\n", " {\n", " //关键点:这里的 += 不是原子操作,相当于 先\"加\"再\"赋值\"\n", " TotalLoopNumber.Value += 1;\n", " \n", " Thread.Sleep(1);\n", " }\n", " Console.WriteLine($\"线程[{Thread.CurrentThread.ManagedThreadId.ToString(\"000\")}] 执行了 {LoopNumber} 次,结束时 TotalLoopNumber = {TotalLoopNumber.Value}\");\n", " }\n", "}\n", "{\n", " var computer = new Computer4();\n", " var threads = new List()\n", " {\n", " new Thread(()=>computer.SetTotalLoopNumber()){ Name = \"thread_a\" },\n", " new Thread(()=>computer.SetTotalLoopNumber()){ Name = \"thread_b\" }\n", " };\n", "\n", " threads.ForEach(t => t.Start());\n", " threads.ForEach(t => t.Join());\n", "\n", " //主线程(内核)调用类的静态字段\n", " Console.WriteLine($\"主(执行)线程[{Thread.CurrentThread.ManagedThreadId.ToString(\"000\")}] 结束时,TotalLoopNumber = {computer.TotalLoopNumber}\");\n", "}\n", "\n", "//使用 ThreadLocal 泛型类,实现线程本地存储:各线程独立互不影响\n", "//多次执行输出一致\n", "//因为主线未运行实例方法,所以主线程本地存储共享变量的值为 0" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## AsyncLocal 泛型类 异常的实现" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "异步方法实现本地存储:因为异步方法的执行时,await 前后的代码可能不在同一线程上执行(即回调方法的执行可能在新线程上),所以异步方法中使用 AsyncLocal" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "//使用 AsynLocal 泛型类,针对异步方法实现线程的本地存储\n", "public class Computer5\n", "{\n", " //循环总次数\n", " public AsyncLocal TotalLoopNumber = new AsyncLocal();\n", " //方法循环次数\n", " public readonly int LoopNumber = 100;\n", "\n", " //循环业务\n", " public Task SetTotalLoopNumberAsync()\n", " {\n", " for (int i = 1; i <= LoopNumber; i++)\n", " {\n", " //关键点:这里的 += 不是原子操作,相当于 先\"加\"再\"赋值\"\n", " TotalLoopNumber.Value += 1;\n", " \n", " Thread.Sleep(1);\n", " }\n", " Console.WriteLine($\"线程[{Thread.CurrentThread.ManagedThreadId.ToString(\"000\")}] 执行了 {LoopNumber} 次,结束时 TotalLoopNumber = {TotalLoopNumber.Value}\");\n", " return Task.CompletedTask;\n", " }\n", "}\n", "{\n", " var computer = new Computer5();\n", " var threads = new List()\n", " {\n", " new Thread(async () => await computer.SetTotalLoopNumberAsync()){ Name = \"thread_a\" },\n", " new Thread(async () => await computer.SetTotalLoopNumberAsync()){ Name = \"thread_b\" }\n", " };\n", "\n", " threads.ForEach(t => t.Start());\n", " threads.ForEach(t => t.Join());\n", "\n", " //主线程\n", " Console.WriteLine($\"主(执行)线程[{Thread.CurrentThread.ManagedThreadId.ToString(\"000\")}] 结束时,TotalLoopNumber = {computer.TotalLoopNumber.Value}\");\n", "}\n", "\n", "//使用 AsynLocal 泛型类,实现异步方法的线程本地存储\n", "//多次执行输出一致\n", "//因为主线未运行实例方法,所以主线程本地存储共享变量的值为 0" ] } ], "metadata": { "kernelspec": { "display_name": ".NET (C#)", "language": "C#", "name": ".net-csharp" }, "language_info": { "name": "polyglot-notebook" }, "polyglot_notebook": { "kernelInfo": { "defaultKernelName": "csharp", "items": [ { "aliases": [], "name": ".NET" }, { "aliases": [ "C#", "c#" ], "languageName": "C#", "name": "csharp" }, { "aliases": [ "F#", "f#" ], "languageName": "F#", "name": "fsharp" }, { "aliases": [], "languageName": "HTML", "name": "html" }, { "aliases": [ "js" ], "languageName": "JavaScript", "name": "javascript" }, { "aliases": [], "languageName": "KQL", "name": "kql" }, { "aliases": [], "languageName": "Mermaid", "name": "mermaid" }, { "aliases": [ "powershell" ], "languageName": "PowerShell", "name": "pwsh" }, { "aliases": [], "languageName": "SQL", "name": "sql" }, { "aliases": [], "name": "value" }, { "aliases": [ "frontend" ], "name": "vscode" }, { "aliases": [], "name": "webview" } ] } } }, "nbformat": 4, "nbformat_minor": 2 }