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 序列化/反序列化代码,告别体力劳动.
随机推荐
- JS 高级程序设计3.5.1一元操作符 递增和递减操作符++ --
var age =29 ++age; 在这个例子中,前置递增操作符把age的值变成了30.实际上,执行这个前置递增操作符与执行 一下操作的效果相同: var age=29; age =age+1;// ...
- ES6 属性方法简写一例:vue methods 属性定义方法
const o = { method() { return "Hello!"; } }; // 等同于 const o = { method: function() { retur ...
- 【阅读笔记】Java核心技术卷一 #1.Chapter3
3 Java的基本程序设计结构 3.1 一个简单的 Java 应用程序(略) 3.2 注释(略) 3.3 数据类型 8种基本类型 byte,short,int,long float,double ch ...
- jstack 命令使用经验总结
jstack 命令的基本使用 jstack 在命令使用上十分简洁, 其信息量与复杂度主要体如今 thread dump 内容的分析上;web # 最基本的使用sudo -u xxx jstack {v ...
- shiro反序列化550、721
shiro550反序列化 获取docker镜像 docker pull medicean/vulapps:s_shiro_1 重启docker systemctl restart docker 启动d ...
- 洛谷P3052题解
题面 看起来非常简单,但是细节多的一批的状压DP入门题. 我设 \(f_i\) 为 \(i\) 状态时最小分组数, \(g_i\) 为 \(i\) 状态时最后一组剩余空间. 对于每一个 \(i\) , ...
- vue菜单切换
HTML: <div id="box"> <ul> <li v-for= "(item,index) in arry"> & ...
- 虚拟基站(VRS)
虚拟参考站技术(Virtual Reference Station,简称VRS)也称虚拟基准站技术,是一种网络实时动态测量实时动态测量(RTK)技术,通过在某一区域内建立构成网状覆盖的多个GPS基 ...
- brew换源
转自:https://blog.csdn.net/gorwayne/article/details/107359912 第一步,替换brew.git cd "$(brew --repo)&q ...
- JVM钩子函数的使用
一.问题引入 背景 在编写一个需要持续在后台运行的程序的时候遇到了这样的场景:我的程序在主函数中创建了一个线程池周期性地执行任务,我希望主线程和线程池都持续运行,但如果收到外部的关闭信号时,主线程和线 ...