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 序列化/反序列化代码,告别体力劳动.
随机推荐
- FactoryBean简介以及Mybatis-Spring应用
一.BeanFactory和FactoryBean区别? BeanFactory是工厂类,提供了获取和检索Bean的接口.它代表着Spring的IoC容器,负责Bean实例化以及管理Bean之间的依赖 ...
- 公有组件ShowCodeList实现原理之一一下拉框的实现
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- SQL语句(四)联表查询
目录 一.关联查询的分类 按年代分 按功能分 二.sql92语法的连接 语法 1. 简单应用 2. 为表起别名 3. 加入筛选 4. 加入分组 5. 三表连接 6. 非等值连接 7. 自连接 三.sq ...
- 数据库技术中的触发器(Trigger)——和ContentObserver功能类似
刚总结过ContentObserver的作用和特点,顺便总结下数据库技术中的触发器(Trigger),触 发 器 分 为 表 触 发 器 . 行 触 发 器
- AndroidStudio快捷键总结
本文总结一下最近常用的命令: 1.格式化代码:Ctrl+Alt+L[可能与电脑快捷键冲突,修改方法:在设置里点击Keymap 搜索:Reformat Code] 2.复制当前类:Ctrl+Shift+ ...
- 数据结构与算法 java描述 第一章 算法及其复杂度
目录 数据结构与算法 java描述 笔记 第一章 算法及其复杂度 算法的定义 算法性能的分析与评价 问题规模.运行时间及时间复杂度 渐进复杂度 大 O 记号 大Ω记号 Θ记号 空间复杂度 算法复杂度及 ...
- “百度杯”CTF比赛 十月场-Getflag(md5碰撞+sql注入+网站绝对路径)
进去md5碰撞,贴一下脚本代码 import hashlib def md5(value): return hashlib.md5(str(value).encode("utf-8" ...
- 数据结构与算法-排序(六)堆排序(Heap Sort)
摘要 堆排序需要用到一种数据结构,大顶堆.大顶堆是一种二叉树结构,本质是父节点的数大于它的左右子节点的数,左右子节点的大小顺序不限制,也就是根节点是最大的值. 这里就是不断的将大顶堆的根节点的元素和尾 ...
- Java多线程操作同一个对象,线程不安全
Java多线程操作同一个对象 发现问题:多个线程操作同一资源的情况下,线程不安全,数据紊乱 代码: package multithreading; // Java多线程操作同一个对象 // 买火车票的 ...
- 【笔记】使用scikit-learn解决回归问题
使用sklearn解决回归问题 依然是加载数据 import numpy as np import matplotlib.pyplot as plt from sklearn import datas ...