diff --git a/Docs/Jupyter笔记.2.4.线程本地存储.ipynb b/Docs/Jupyter笔记.2.4.线程本地存储.ipynb index 6625e90..97c6b8e 100644 --- a/Docs/Jupyter笔记.2.4.线程本地存储.ipynb +++ b/Docs/Jupyter笔记.2.4.线程本地存储.ipynb @@ -3,14 +3,7 @@ { "attachments": {}, "cell_type": "markdown", - "metadata": { - "dotnet_interactive": { - "language": "csharp" - }, - "polyglot_notebook": { - "kernelName": "csharp" - } - }, + "metadata": {}, "source": [ "线程本地存储\n", "============================== " @@ -19,49 +12,34 @@ { "attachments": {}, "cell_type": "markdown", - "metadata": { - "dotnet_interactive": { - "language": "csharp" - }, - "polyglot_notebook": { - "kernelName": "csharp" - } - }, + "metadata": {}, "source": [ "线程本地存储(Thread Local Storge,简称TLS)机制用于实现按线程隔离的线程本地变量,对于同一个线程本地变量,各个线程分别有独立的值,修改的值只能修改的线程可见。
\n", "\n", "线程要地存储可以分为原生实现与托管实现,原生实现指的是调用操作系统提供的接口访问原生线程对应的线程本地存储,而托管实现指的是调用.net提供的接口访问托管线程对应的线程本地存储。
\n", "\n", - "原生实现,在Windows和linux上的实现不同,但同样都是调用系统提供的接口。
" + "原生实现,在Windows和linux上的实现不同,但同样都是调用系统提供的接口。
\n", + "\n", + "注意:线程本地存储目的是每线程复制一份数据,做到线程独立与隔离,而不是使用共享数据与同步(使用与同步实现在原子操作、锁、信号量等方面)。" ] }, { "attachments": {}, "cell_type": "markdown", - "metadata": { - "dotnet_interactive": { - "language": "csharp" - }, - "polyglot_notebook": { - "kernelName": "csharp" - } - }, + "metadata": {}, "source": [ "## 全局设置,语言设置、Nuget包引用、空间引用等" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" - }, - "vscode": { - "languageId": "polyglot-notebook" } }, "outputs": [], @@ -120,29 +98,15 @@ { "attachments": {}, "cell_type": "markdown", - "metadata": { - "dotnet_interactive": { - "language": "csharp" - }, - "polyglot_notebook": { - "kernelName": "csharp" - } - }, + "metadata": {}, "source": [ - "## ThreadStatic Attribute 属性的实现" + "## ThreadStatic Attribute 特性 实现" ] }, { "attachments": {}, "cell_type": "markdown", - "metadata": { - "dotnet_interactive": { - "language": "csharp" - }, - "polyglot_notebook": { - "kernelName": "csharp" - } - }, + "metadata": {}, "source": [ "在 .NET 中标记了[ThreadStatic]特性的全局变量就是托管线程本地变量。
\n", "\n", @@ -150,52 +114,99 @@ ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "dotnet_interactive": { - "language": "csharp" - }, - "polyglot_notebook": { - "kernelName": "csharp" - }, - "vscode": { - "languageId": "polyglot-notebook" - } - }, - "outputs": [], + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, "source": [ - "{ //隔离代码\n", - "\n", - " Console.WriteLine(\"花括号隔离代码,避免上下段互相影响!\");\n", - "}" + "注意:ThreadStatic 特性,只支持静态字段,不支持实例字段(实例字段不启作用)。" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" - }, - "vscode": { - "languageId": "polyglot-notebook" } }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "计算结果:210\n", + "计算结果:210\n", + "210\n", + "30 31 30 31 31 30 31 30 30 30 31 30 30 31 30\n" + ] + } + ], "source": [ + "///\n", + "/// 无\"ThreadStatic\"特性:关键点是因为对共享变量Total的操作(++ -- += *=)不是原子性的\n", + "/// 多线程时,可能会交叉执行,造成线程不安全。线程本地变量是使共享对象在每个线程都复制一份,做倒线程独立、互不影响。也可以使用原子操作代替,形成无锁算法。\n", + "///\n", "public class Demo\n", "{\n", + " //[ThreadStatic]\n", + " public static double Total = 0;\n", + " public static List threadIds = new List();\n", + " public double Sum()\n", + " {\n", + " //double temp = 0;\n", + " for (int i = 1; i <= 10; i++)\n", + " {\n", + " //temp = Total;\n", + " //关键点:这里的 += 不是原子操作,相当于 先\"加\"再\"赋值\"\n", + " Total += i;\n", "\n", - " \n", + " double tem = Total + i;\n", + " Total = tem;\n", + " \n", + " threadIds.Add(Thread.CurrentThread.ManagedThreadId);\n", + " Thread.Sleep(100);\n", + "\n", + " //Console.WriteLine($\"线程{Thread.CurrentThread.ManagedThreadId.ToString(\"000\")}: {temp.ToString(\"00000000\")} + {i.ToString(\"00000000\")} = {Total}\");\n", + " }\n", + " Console.WriteLine($\"计算结果:{Total}\");\n", + " return Total;\n", + " }\n", "}\n", - "{\n", "\n", + "Demo.Total =100;\n", + "var demo1 = new Demo();\n", + "var thread_a = new Thread(()=>demo1.Sum());\n", + "var thread_b = new Thread(()=>demo1.Sum());\n", "\n", - "}" + "thread_a.Start();\n", + "thread_b.Start();\n", + " \n", + "thread_a.Join();\n", + "thread_b.Join();\n", + "\n", + "//主线程(内核)调用类的静态字段\n", + "Console.WriteLine(Demo.Total);\n", + "\n", + "Console.WriteLine($\"{string.Join(\" \",Demo.threadIds)}\");\n" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 数据槽 实现" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ThreadLocal 泛型类 实现" ] } ], @@ -205,34 +216,29 @@ "language": "C#", "name": ".net-csharp" }, + "language_info": { + "name": "polyglot-notebook" + }, "polyglot_notebook": { "kernelInfo": { "defaultKernelName": "csharp", "items": [ { - "aliases": [ - "c#", - "C#" - ], - "languageName": "C#", - "name": "csharp" + "aliases": [], + "name": ".NET" }, { "aliases": [ - "frontend" + "C#", + "c#" ], - "languageName": null, - "name": "vscode" - }, - { - "aliases": [], - "languageName": null, - "name": ".NET" + "languageName": "C#", + "name": "csharp" }, { "aliases": [ - "f#", - "F#" + "F#", + "f#" ], "languageName": "F#", "name": "fsharp" @@ -273,9 +279,14 @@ }, { "aliases": [], - "languageName": null, "name": "value" }, + { + "aliases": [ + "frontend" + ], + "name": "vscode" + }, { "aliases": [], "name": "webview" diff --git a/MultiThreadingStudy.ConsoleApp/Program.cs b/MultiThreadingStudy.ConsoleApp/Program.cs index 63b6bc2..68eea3f 100644 --- a/MultiThreadingStudy.ConsoleApp/Program.cs +++ b/MultiThreadingStudy.ConsoleApp/Program.cs @@ -1,10 +1,18 @@ +using MultiThreadingStudy.Core; + namespace MultiThreadingStudy.ConsoleApp { - internal class Program + public class Program { static void Main(string[] args) { Console.WriteLine("多线程学习"); + + ThreadLocalStorage.Run(); + ThreadLocalStorage.UseDataSlot(); + ThreadLocalStorage.UseThreadStaticAttribute(); + ThreadLocalStorage.UseThreadLocalClass(); + } } } \ No newline at end of file diff --git a/MultiThreadingStudy.Core/Person.cs b/MultiThreadingStudy.Core/Person.cs deleted file mode 100644 index 6815196..0000000 --- a/MultiThreadingStudy.Core/Person.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace MultiThreadingStudy.Core -{ - public class Person - { - public int Id { get; set; } - public string? Name { get; set; } - public string? Address { get; set; } - public int Age { get; set; } - - } -} diff --git a/MultiThreadingStudy.Core/ThreadLocalStorage.cs b/MultiThreadingStudy.Core/ThreadLocalStorage.cs new file mode 100644 index 0000000..15667c8 --- /dev/null +++ b/MultiThreadingStudy.Core/ThreadLocalStorage.cs @@ -0,0 +1,254 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +namespace MultiThreadingStudy.Core +{ + /// + /// 线程本地存储 执行器 + /// + public class ThreadLocalStorage + { + /// + /// 不使用线程本地存储 + /// + public static void Run() + { + var demo = new Computer(); + var thread_a = new Thread(new ThreadStart(demo.SumCount)) { Name = "thread_a" }; + var thread_b = new Thread(new ThreadStart(demo.SumCount)) { Name = "thread_b" }; + + thread_a.Start(); + thread_b.Start(); + + thread_a.Join(); + thread_b.Join(); + + //调用线程:[主线程|内核线程] 共享字段的值。不加处理的话,可能小于多线程调用次数之和。 + Console.WriteLine("调用线程的共享变量值 = " + Computer.TotalCount); + } + + /// + /// 使用 ThreadStatic 特性 + /// + + public static void UseThreadStaticAttribute() + { + var demo = new ThreadStaticComputer(); + var thread_a = new Thread(new ThreadStart(demo.SumCount)) { Name = "thread_a" }; + var thread_b = new Thread(new ThreadStart(demo.SumCount)) { Name = "thread_b" }; + + thread_a.Start(); + thread_b.Start(); + + thread_a.Join(); + thread_b.Join(); + + //调用线程:[主线程|内核线程] 共享字段的值。不加处理的话,可能小于多线程调用次数之和。 + Console.WriteLine("调用线程的共享变量值 = " + Computer.TotalCount); + } + + /// + /// 使用数据槽 + /// + public static void UseDataSlot() + { + var demo = new DataSlotComputer(); + var thread_a = new Thread(new ThreadStart(demo.SumCount)) { Name = "thread_a" }; + var thread_b = new Thread(new ThreadStart(demo.SumCount)) { Name = "thread_b" }; + + thread_a.Start(); + thread_b.Start(); + + thread_a.Join(); + thread_b.Join(); + + //调用线程:[主线程|内核线程] 共享字段的值。不加处理的话,可能小于多线程调用次数之和。 + Console.WriteLine("调用线程的共享变量值 = " + Computer.TotalCount); + } + + /// + /// 使用 ThreadLocal 泛型类 + /// + public static void UseThreadLocalClass() + { + var demo = new ThreadLocalComputer(); + var thread_a = new Thread(new ThreadStart(demo.SumCount)) { Name = "thread_a" }; + var thread_b = new Thread(new ThreadStart(demo.SumCount)) { Name = "thread_b" }; + + thread_a.Start(); + thread_b.Start(); + + thread_a.Join(); + thread_b.Join(); + + //调用线程:[主线程|内核线程] 共享字段的值。不加处理的话,可能小于多线程调用次数之和。 + Console.WriteLine("调用线程的共享变量值 = " + Computer.TotalCount); + } + } + + /// + /// 不使用本地存储 + /// + /// + /// 由于 ++ -- += -= 等对共享变量的操作不是原子性的,多线程下是不安全的。 + /// + public class Computer + { + /// + /// 总计算次数 + /// + //[ThreadStatic] + public static int TotalCount = 0; + + /// + /// 循环次数 + /// + public readonly int LoopNumber = 1000; + + /// + /// 累加计数 + /// + public void SumCount() + { + for (int i = 1; i <= LoopNumber; i++) + { + //因为 += 是和等于的两步操作,所以不是线程安全的。 + TotalCount += 1; + + //如果使用原子操作,则是线程安全的 + //Interlocked.Increment(ref TotalCount); + + //加休眠是为了让并发效果明显,不加休眠可以增加循环次数 + Thread.Sleep(1); + } + + Console.WriteLine($"线程[{Thread.CurrentThread.ManagedThreadId.ToString("000")}]执行后,共享变量 {nameof(TotalCount)} = {TotalCount}"); + } + } + + /// + /// 使用本地存储:ThreadStatic 特性 + /// + /// + /// 特点:只能用于静态变量上,不能用于实例上(语法不报错,实际不起作用) + /// + public class ThreadStaticComputer + { + /// + /// 总计算次数 + /// + [ThreadStatic] + public static int TotalCount = 0; + + /// + /// 循环次数 + /// + public readonly int LoopNumber = 100; + + /// + /// 累加计数 + /// + public void SumCount() + { + for (int i = 1; i <= LoopNumber; i++) + { + TotalCount += 1; + + Thread.Sleep(0); + } + Console.WriteLine($"线程[{Thread.CurrentThread.ManagedThreadId.ToString("000")}]执行后,共享变量 {nameof(TotalCount)} = {TotalCount}"); + } + } + + /// + /// 使用本地存储:数据槽 + /// + /// + /// 特点:可以用于静态也可用于实例 + /// + public class DataSlotComputer + { + /// + /// 总计算次数 + /// + public static int TotalCount = 0; + + /// + /// 循环次数 + /// + public readonly int LoopNumber = 100; + + /// + /// 数据槽 + /// + public LocalDataStoreSlot storeSlot = Thread.AllocateDataSlot(); + + /// + /// 累加计数值 + /// + public void SumCount() + { + for (int i = 1; i <= LoopNumber; i++) + { + Thread.SetData(storeSlot, i); + + //加休眠是为了让并发效果明显,不加休眠可以增加循环次数 + Thread.Sleep(0); + } + + //严格讲,这里也不是线程安全的。少量线程不安全的概率很小.可以使用原子操作实现。 + TotalCount += (int)Thread.GetData(storeSlot); + //保证线程安全的话,可用原子操作 + //Interlocked.Add(ref TotalCount, (int)Thread.GetData(storeSlot)); + + Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId.ToString("000")}执行后,共享变量{nameof(TotalCount)} = {TotalCount}"); + } + } + + /// + /// 使用本地存储:ThreadLocal泛型类 + /// + /// + /// 特点:1、对静态和实例字段都提供了线程本地存储支持,并允许指定默认值 + /// 2、ThreadLocal的值是延迟计算的:每线程第一次调用时计算实际的值 + /// + public class ThreadLocalComputer + { + /// + /// 总计算次数 + /// + public static int TotalCount = 0; + + /// + /// 循环次数 + /// + public readonly int LoopNumber = 100; + + /// + /// 线程本地存储数据 + /// + public static ThreadLocal threadTotalCount = new ThreadLocal(); + /// + /// 累加计数值 + /// + public void SumCount() + { + for (int i = 1; i <= LoopNumber; i++) + { + + threadTotalCount.Value += 1; + + //加休眠是为了让并发效果明显,不加休眠可以增加循环次数 + Thread.Sleep(0); + } + + TotalCount += threadTotalCount.Value; + //保证线程安全的话,可用原子操作 + //Interlocked.Add(ref TotalCount, threadTotalCount.Value); + + Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId.ToString("000")}执行后,共享变量{nameof(TotalCount)} = {TotalCount}"); + } + } +} diff --git a/MultiThreadingStudy.xUnitTest/ThreadLocalStorageTest.cs b/MultiThreadingStudy.xUnitTest/ThreadLocalStorageTest.cs index 255e2af..fa165b4 100644 --- a/MultiThreadingStudy.xUnitTest/ThreadLocalStorageTest.cs +++ b/MultiThreadingStudy.xUnitTest/ThreadLocalStorageTest.cs @@ -1,4 +1,6 @@ -using System; +using MultiThreadingStudy.Core; + +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -11,8 +13,38 @@ namespace MultiThreadingStudy.xUnitTest /// public class ThreadLocalStorageTest { - public ThreadLocalStorageTest() - { + private readonly ITestOutputHelper output; + + public ThreadLocalStorageTest(ITestOutputHelper outputHelper) + { + output = outputHelper; + } + + [Fact] + public void Computer_Test() + { + Computer computer = new Computer(); + + List threads = new List() + { + new Thread(new ThreadStart(computer.SumCount)) { Name = "thread_a" }, + new Thread(new ThreadStart(computer.SumCount)) { Name = "thread_b" }, + }; + + foreach (Thread thread in threads) + { + thread.Start(); + } + + foreach (Thread thread in threads) + { + thread.Join(); + } + + output.WriteLine($"调用线程 TotalCount = " + Computer.TotalCount); + + //不严谨:应该是<= + Assert.True(Computer.TotalCount < computer.LoopNumber * threads.Count); } } }