{ "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "原子操作\n", "=======" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "在多线程环境中,因为多个线程可能会同时访问同一个资源,我们需要使用一些方式保证访问不发生冲突,其中最基础的方式就是原子操作。 \n", "原子操作是利用 CPU 提供的特性,所以不会阻塞线程。 \n", "线程安全方式有: \n", "+ 使用设计,避免多线程中的资源共享\n", "+ 利用线程本地存储,使资源复制到线程本地,实现互相独立、互不干扰\n", "+ 使用原子操作\n", "+ 使用锁\n", "+ 使用信号量\n", "+ 综合使用" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 全局设置,语言设置、Nuget包引用、空间引用等" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "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": [ "## .Net中的原子操作类" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ ".Net中, System.Threading.Interlocked 类提供了用于执行原子操作的函数,这些函数接收引用参数(ref),也就是变量内存地址,然后针对该内存地址的值执行原子操作。" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### 原子递增 Interlocked.Increment()\n", "Interlocked.Increment 函数执行的原子操作属于 “获取-添加”分类,执行后变量的值增加1,返回的值是增加后的值,即增加前的值加1" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [], "source": [ "//计数器类\n", "public class Counter\n", "{\n", " //总次数\n", " public static int TotalNumber = 0;\n", " \n", " //方法循环次数\n", " public static readonly int LoopNumber = 100;\n", "\n", " //执行\n", " public static void Execute()\n", " {\n", " for (int i = 1; i <= LoopNumber; i++)\n", " {\n", " //原子操作\n", " System.Threading.Interlocked.Increment(ref TotalNumber);\n", " }\n", " Console.WriteLine($\"线程[{Thread.CurrentThread.ManagedThreadId.ToString(\"000\")}] 执行了累加 {LoopNumber} 次,结束时 TotalNumber = {TotalNumber}\");\n", " }\n", "}\n", "\n", "//使用域隔离和代码折叠\n", "{\n", " var threads = new List()\n", " {\n", " new Thread(Counter.Execute){Name=\"thread_a\"},\n", " new Thread(Counter.Execute){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 = {Counter.TotalNumber}\");\n", "}\n", "\n", "//原子操作,多线程下多次执行结果相同" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## 原子递减 Interlocked.Decrement()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "属于 `获取-添加`分类,执行后变量的值减少1,返回值是减少后的值即原值减1" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [], "source": [ "#region 原子递减\n", "//原子递减\n", "public class Counter4Decrement\n", "{\n", " //总次数\n", " public static int TotalNumber = 0;\n", " \n", " //方法循环次数\n", " public static readonly int LoopNumber = 10000;\n", "\n", " //执行\n", " public static void Decrement()\n", " {\n", " for (int i = 1; i <= LoopNumber; i++)\n", " {\n", " //原子操作:循环递减,返回递减后的值\n", " var decreValue = System.Threading.Interlocked.Decrement(ref TotalNumber);\n", " }\n", " Console.WriteLine($\"线程[{Thread.CurrentThread.Name}] 执行了递减 {LoopNumber} 次,结束时 TotalNumber = {TotalNumber}\");\n", " }\n", "}\n", "\n", "//使用域隔离和代码折叠\n", "{\n", " var threads = new List()\n", " {\n", " new Thread(Counter4Decrement.Decrement){Name=\"thread_a\"},\n", " new Thread(Counter4Decrement.Decrement){Name=\"thread_b\"}\n", " };\n", "\n", " threads.ForEach(t => t.Start());\n", " threads.ForEach(t => t.Join());\n", "\n", " //主线程信息\n", " Console.WriteLine($\"主(执行)线程[{Thread.CurrentThread.Name}] 结束时,TotalLoopNumber = {Counter4Decrement.TotalNumber}\");\n", "}\n", "\n", "//原子操作:\n", "//1、多线程下,每线程执行结束后共享变量可能不同\n", "//2、所有线程结束, 主线程获取的变量值与最后完成的线程值相同\n", "#endregion" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## 原子增加指定数量(负数为减) Interlocked.Add()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Interlocked.Add 属于 `获取-添加`分类,执行后变量的值加等于第二个参数的值,返回值是增加后的值即原值加第二个参数值" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [], "source": [ "//原子增加指定数量\n", "public class Counter4Add\n", "{\n", " //共享变量\n", " public static int ShareNumber = 0;\n", "\n", " //执行\n", " public static void Add()\n", " {\n", " //随机休眠,打乱多线程执行顺序\n", " Thread.Sleep(Random.Shared.Next(100,500));\n", "\n", " //原子操作:增加指定数量\n", " var decreValue = System.Threading.Interlocked.Add(ref ShareNumber,2);\n", " Console.WriteLine($\"线程[{Thread.CurrentThread.Name}] 递减2后, ShareNumber = {ShareNumber}\");\n", " }\n", "}\n", "\n", "//使用域隔离和代码折叠\n", "{\n", " var threads = new List()\n", " {\n", " new Thread(Counter4Add.Add){Name=\"thread_a\"},\n", " new Thread(Counter4Add.Add){Name=\"thread_b\"}\n", " };\n", "\n", " threads.ForEach(t => t.Start());\n", " threads.ForEach(t => t.Join());\n", "\n", " //主线程信息\n", " Console.WriteLine($\"主[执行]线程获共享变量 ShareNumber 最终的值 = {Counter4Add.ShareNumber}\");\n", "}\n", "\n", "//原子操作:\n", "//1、两个线程同时执行会输出: 2 4 或 4 2\n", "//2、全局变量最终的值为 4" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## 原子替换 Interlocked.Exchange()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Interlocked.Exchange() 属于 `测试-设置` 分类, 将变量的值修改为第二个参数的值,返回修改前的值" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [], "source": [ "//原子替换\n", "public class Counter4Exchange\n", "{\n", " //共享变量\n", " public static int ShareNumber = 0;\n", "\n", " //执行\n", " public static void Exchange()\n", " {\n", " //随机休眠,打乱多线程执行顺序\n", " Thread.Sleep(Random.Shared.Next(100,500));\n", "\n", " //原子操作:替换\n", " var oldValue = System.Threading.Interlocked.Exchange(ref ShareNumber,1);\n", " Console.WriteLine($\"线程[{Thread.CurrentThread.Name}] 替换前的值为 {oldValue}\");\n", " }\n", "}\n", "\n", "//使用域隔离和代码折叠\n", "{\n", " var threads = new List()\n", " {\n", " new Thread(Counter4Exchange.Exchange){Name=\"thread_a\"},\n", " new Thread(Counter4Exchange.Exchange){Name=\"thread_b\"}\n", " };\n", "\n", " threads.ForEach(t => t.Start());\n", " threads.ForEach(t => t.Join());\n", "\n", " //主线程信息\n", " Console.WriteLine($\"主[执行]线程获共享变量 ShareNumber 最终的值 = {Counter4Exchange.ShareNumber}\");\n", "}\n", "\n", "//原子替换:\n", "//1、两个线程同时执行会输出: 0 1 或 1 0\n", "//2、全局变量最终的值为 1" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## 原子比较替换 Interlocked.CompareExchange()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Interlocked.CompareExchange() 属于 `比较-交换` 分类, 执行时如果变量的值等于第三个变量的值,则修改为第二个变量的值并返回修改前的值;否则直接返回现有值" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [], "source": [ "//原子比较替换\n", "public class Counter4CompareExchange\n", "{\n", " //共享变量\n", " public static int ShareNumber = 0;\n", "\n", " //执行\n", " public static void CompareExchange()\n", " {\n", " //随机休眠,打乱多线程执行顺序\n", " Thread.Sleep(Random.Shared.Next(100,500));\n", "\n", " //原子操作:比较替换\n", " int y;\n", " while(true)\n", " {\n", " y = ShareNumber;\n", " int oldShareValue = System.Threading.Interlocked.CompareExchange(ref ShareNumber,y+1,y);\n", "\n", " if(oldShareValue == y)\n", " {\n", " break;\n", " }\n", " }\n", "\n", " Console.WriteLine($\"线程[{Thread.CurrentThread.Name}] 替换前的值为 {y+1}\");\n", " }\n", "}\n", "\n", "//使用域隔离和代码折叠\n", "{\n", " var threads = new List()\n", " {\n", " new Thread(Counter4CompareExchange.CompareExchange){Name=\"thread_a\"},\n", " new Thread(Counter4CompareExchange.CompareExchange){Name=\"thread_b\"}\n", " };\n", "\n", " threads.ForEach(t => t.Start());\n", " threads.ForEach(t => t.Join());\n", "\n", " //主线程信息\n", " Console.WriteLine($\"主[执行]线程获共享变量 ShareNumber 最终的值 = {Counter4CompareExchange.ShareNumber}\");\n", "}\n", "\n", "//原子替换:\n", "//1、两个线程同时执行会输出: 1 2 或 2 1\n", "//2、全局变量最终的值为 2" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## 原子读取 Interlocked.Read()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "从内存读取值通常都是原子的,除非32位平台读取64位数据,比如32位平台上读取 long 型数值。因为32位平台读取64位数据是分高位和低位两次读取操作的,所以不是原子操作。原子读取一般会与其它原子操作配合使用。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [], "source": [ "//原子读取\n", "public class Counter4Read\n", "{\n", " //共享变量,64位数据\n", " public static long ShareNumber = 0;\n", "\n", " //循环次数\n", " public readonly static int LoopNumber = 10000;\n", "\n", " //一个线程循环替换数据\n", " public static void Exchange()\n", " {\n", " for(int i=0; i< LoopNumber; i++)\n", " {\n", " System.Threading.Interlocked.Exchange(ref ShareNumber,0x7aaabbbbccccdddd);\n", " }\n", " }\n", " \n", " //一个线程循环读取\n", " //无论多少次读取的都是 0 或者 0x7aaabbbbccccdddd\n", " //不可能是 0x7aaabbbb00000000 或者 ccccdddd\n", " public static void Read()\n", " {\n", " for(int i=0; i< LoopNumber; i++)\n", " {\n", " long y = System.Threading.Interlocked.Read(ref ShareNumber);\n", "\n", " //此条件应该永远不成立\n", " if(y==0x7aaabbbb00000000 || y==0x00000000ccccdddd)\n", " { \n", " Console.WriteLine($\"线程[{System.Threading.Thread.CurrentThread.Name}], 读取到非原子值{y}\");\n", " }\n", " }\n", " }\n", "}\n", "\n", "//使用域隔离和代码折叠\n", "{\n", " var threads = new List()\n", " {\n", " new Thread(Counter4Read.Exchange){Name=\"thread_a\"},\n", " new Thread(Counter4Read.Read){Name=\"thread_b\"}\n", " };\n", "\n", " threads.ForEach(t => t.Start());\n", " threads.ForEach(t => t.Join());\n", "\n", " //主线程信息\n", " Console.WriteLine($\"主[执行]线程获共享变量 ShareNumber 最终的值 = {Counter4Read.ShareNumber}\");\n", "}\n", "\n", "//原子读取:\n", "//1、无论多少次读取的都是 0 或者 0x7aaabbbbccccdddd 不可能是 0x7aaabbbb00000000 或者 ccccdddd\n", "//2、全局变量最终的值为 0x7aaabbbbccccdddd 即是 8839083633937276381 或者极概率是 0" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## 内存屏障 Interlocked.MemoryBarrier()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "System.Threading.Interlocked 还提供了一些其它方法,比如内存屏障相关的 MemoryBarrier() 和 MemoryBarrierProcessWide() 和 新的 And() Or()方法等。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [], "source": [ "/*\n", "System.Threading.Interlocked.MemoryBarrier();\n", "System.Threading.Interlocked.MemoryBarrierProcessWide();\n", "System.Threading.Interlocked.And();\n", "System.Threading.Interlocked.Or();\n", "*/" ] } ], "metadata": { "kernelspec": { "display_name": ".NET (C#)", "language": "C#", "name": ".net-csharp" }, "language_info": { "file_extension": ".cs", "mimetype": "text/x-csharp", "name": "C#", "pygments_lexer": "csharp", "version": "11.0" }, "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 }