dotnet C# 给结构体字段赋值非线程安全
在 dotnet 运行时中,给引用对象进行赋值替换的时候,是线程安全的。给结构体对象赋值,如果此结构体是某个类的成员字段,那么此赋值不一定是线程安全的。是否线程安全,取决于结构体的大小,取决于此结构体能否在一次原子赋值内完成
大家都知道,某个执行逻辑如果是原子逻辑,那么此逻辑是线程安全的。原子逻辑就是一个非 A 即 B 的状态的变更,绝对不会存在处于 A 和 B 中间的状态。满足于此即可称为线程安全,因为线程不会读取到中间状态。在 dotnet 运行时里面,特别对了引用对象,也就是类对象的赋值过程进行了优化,可以让对象的赋值是原子的
从运行时的逻辑上,可以了解到的是引用对象的赋值本质上就是将新对象的引用地址赋值,对象引用地址可以认为是指针。在单次 CPU 运算中可以一次性完成,不会存在只写入某几位而还有某几位没有写入的情况

大概可以认为在 x86 上,单次的原子赋值长度就是 32 位。这也就是为什么 dotnet 里面的对象地址设计为 32 位的原因
但是对于结构体来说,需要分为两个情况,定义在栈上的结构体,如某个方法的局部变量或参数是结构体,此时的结构体是存放在栈上的,而在 dotnet 里面,每个线程都有自己独立的栈,因此放在栈上的结构体在线程上是独立的,相互之间没有影响,也就是线程安全的
如果是放在堆上面的结构体,如作为某个类对象的字段,此时的结构体将会占用此类对象的内存空间,如对以下代码的内存示意图
    class Foo
    {
        public int X; // 没有任何项目或理由可以公开字段,本文这里不规范的写法仅仅只是为了做演示而已 (Unity除外)
        public FooStruct FooStruct;
        public int Y;
    }
    struct FooStruct
    {
        public int A { set; get; }
        public int B { set; get; }
        public int C { set; get; }
        public int D { set; get; }
    }
此时的 Foo 对象在内存上的布局示意图大概如下

如上面示意图,在内存布局上,将会在类内存布局上将结构体展开,占用类的一段内存空间。也就是说本质上结构体如命名,就是多个基础类型的组合,实际上是运行的概念。也就是说在给类对象的字段是结构体进行赋值的时候,每次赋值的内容仅仅是取决于原子长度,如 x86 下使用 32 位进行赋值,相当于先给 FooStruct 的 A 进行赋值,再给 FooStruct 的 B 进行赋值等等。此时如果有某个线程在进行赋值,某个线程在进行读取 Foo 对象的 FooStruct 字段,那么也许读取的线程会读取到正在赋值到一半的 FooStruct 结构体
如以下的测试代码
    class Program
    {
        static void Main(string[] args)
        {
            var taskList = new List<Task>();
            for (int i = 0; i < 100; i++)
            {
                var n = i;
                taskList.Add(Task.Run(() =>
                {
                    while (Foo != null)
                    {
                        var fooStruct = new FooStruct()
                        {
                            A = n,
                            B = n,
                            C = n,
                            D = n
                        };
                        Foo.FooStruct = fooStruct;
                        fooStruct = Foo.FooStruct;
                        var value = fooStruct.A;
                        if (fooStruct.B != value)
                        {
                            throw new Exception();
                        }
                        if (fooStruct.C != value)
                        {
                            throw new Exception();
                        }
                        if (fooStruct.D != value)
                        {
                            throw new Exception();
                        }
                    }
                }));
            }
            Task.WaitAll(taskList.ToArray());
        }
        private static Foo Foo { get; } = new Foo();
    }
以上代码开启了很多线程,每个线程都在尝试读写此结构体。每次写入的赋值都是在 A B C D 给定相同的一个数值,在读取的时候判断是否读取到的每一个属性是否都是相同的数值,如果存在不同的,那么证明给结构体赋值是线程不安全的
运行以上代码,可以看到,在结构体中,存在属性的数值是不相同的。通过以上代码可以看到,放在类对象的字段的结构体,进行赋值是线程不安全的
可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 01a988dd6efdd0550ce0302ecbb93755f1720e85
以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
获取代码之后,进入 YanibeyeNelahallfaihair 文件夹
dotnet C# 给结构体字段赋值非线程安全的更多相关文章
- ANSI C中取得结构体字段偏移量的常用方法
		来自http://blog.chinaunix.net/u2/62910/showart_492571.html 假设在ANSI C程序中定义了一个名为MyStruct的结构类型,其中有一个名为MyF ... 
- hdu 6113 度度熊的01世界(结构体的赋值问题)
		题目大意: 输入n*m的字符串矩形,判断里面的图形是1还是0,还是什么都不是 注意:结构体中放赋值函数,结构体仍旧能定义的写法 #include <iostream> #include&l ... 
- go语言通过反射获取和设置结构体字段值的方法
		本文实例讲述了go语言通过反射获取和设置结构体字段值的方法.分享给大家供大家参考.具体实现方法如下: type MyStruct struct { N int } n := MyStruct{ 1 } ... 
- C和指针 第十二章 结构体 整体赋值 error: expected expression
		定义结构体后整体赋值时发生错误 typedef struct NODE { struct NODE *fwd; struct NODE *bwd; int value; } Node; //声明变量 ... 
- (转)关于linux中内核编程中结构体的赋值操作(结构体指定初始化)
		网址:http://blog.chinaunix.net/uid-24807808-id-3219820.html 在看linux源码的时候,经常会看到类似于下面的结构体赋值的代码: struct d ... 
- PAT 甲级 1032 Sharing (25 分)(结构体模拟链表,结构体的赋值是深拷贝)
		1032 Sharing (25 分) To store English words, one method is to use linked lists and store a word let ... 
- C语言 结构体指针赋值 incompatible types when assigning to type 'char[20]' from type 'char *'
		strcpy(pstudent->name, "guo zhao wei "); 为什么错误,该怎么写,(红色行) 追问 为什么不能直接赋值啊, 追答 用char n ... 
- C结构体数组赋值
		#include <stdio.h> #include <stdlib.h> struct MyStruct { int a; char b; }; struct MyStru ... 
- 现代 C++ 编译时 结构体字段反射
		基于 C++ 14 原生语法,不到 100 行代码:让编译器帮你写 JSON 序列化/反序列化代码,告别体力劳动. 
随机推荐
- Skywalking-04:扩展Metric监控信息
			扩展 Metric 监控信息 官方文档 Source and Scope extension for new metrics 案例:JVM Thread 增加 Metrics 修改 Thread 的定 ... 
- netty系列之:中国加油
			目录 简介 场景规划 启动Server 启动客户端 消息处理 消息处理中的陷阱 总结 简介 之前的系列文章中我们学到了netty的基本结构和工作原理,各位小伙伴一定按捺不住心中的喜悦,想要开始手写代码 ... 
- Docker入门第三章
			配置阿里云镜像加速器 1.首先打开阿里云,搜索容器镜像服务,打开如下 2.配置镜像加速器 sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.j ... 
- GUI编程简介
			GUI编程(淘汰) GUI编程怎么学? 这是什么 它怎么玩 该如何去在我们平时运用 class -- 可阅读 组件 窗口 弹窗 面板 文本框 列表框 按钮 图片 监听事件 鼠标 键盘事件 破解工具 1 ... 
- ABP框架使用Oracle数据库,并实现从SQLServer中进行数据迁移的处理
			ABP框架的数据访问底层是基于EFCore(Entity Framework Core)的,是微软标志性且成熟的ORM,因此它本身是支持多种主流数据库MySQL,SqlServer,Oracle,SQ ... 
- 如何用Git上传项目到GitHub
			1.登录gitHub,进入主页面,点击"+"号,建立新仓库. 2. 输入自己的仓库名,和简单的描述,根据自己设置为公开的或私有的. 我输入的是仓库名为ESMS. 勾选此选项,rea ... 
- ViewPager2 使用说明书
			ViewPager2 使用说明书 零.Demo 项目源码 演示 apk 如果对你有用,希望能给个 star,谢谢. 一.功能 官方关于使用 ViewPager2 创建滑动视图的说明: Swipe vi ... 
- C#给线程传递数据
			目录 0. 前情说明 1. ParameterizedThreadStart类型的委托 2. 使用自定义类 3. 使用Lambda表达式 4. 参考以及文中源代码下载 shanzm-2021年8月24 ... 
- 内存吞金兽(Elasticsearch)的那些事儿 -- 常见问题痛点及解决方案
			1.大数据量的查询效率如何保证: 查询的流程:往 ES 里写的数据,实际上都写到磁盘文件里去了,查询的时候,操作系统会将磁盘文件里的数据自动缓存到 Filesystem Cache 里面去 最佳的情况 ... 
- NOIP 模拟 $36\; \rm Cicada 拿衣服$
			题解 \(by\;zj\varphi\) 发现右端点固定时,左端点的 \(min-max\) 单调递减,且对于 \(or\) 和 \(and\) 相减,最多有 \(\rm2logn\)个不同的值,且相 ... 
