C# .NET ref参数是线程安全的,还是容易受到不安全的多线程访问的攻击?
编辑简介:C# .NET ref参数是线程安全的,还是容易受到不安全的多线程访问的攻击?,c#,.net,parameters,thread-safety,C#,.net,Parameters,Thread Safety,编辑简介: 我们知道,C#中的ref参数将引用传递给变量,从而允许外部变量本身在被调用的方法中进行更改。但是,引用的处理方式很像C指针(每次访问该参数时读取原始变量的当前内容,每次修改参数时更改原始变量),还是被调用的方法在调用期间依赖于一致的引用?前者带来了一些线程安全问题。特别是: 我用C#编写了一个静态方法,通过引用传递对象: public static void Register(ref Definition newDefinition) { ... } 调用者提供一个已完成但尚未注册
我们知道,C#中的ref参数将引用传递给变量,从而允许外部变量本身在被调用的方法中进行更改。但是,引用的处理方式很像C指针(每次访问该参数时读取原始变量的当前内容,每次修改参数时更改原始变量),还是被调用的方法在调用期间依赖于一致的引用?前者带来了一些线程安全问题。特别是: 我用C#编写了一个静态方法,通过引用传递对象:
public static void Register(ref Definition newDefinition) { ... }
调用者提供一个已完成但尚未注册的定义
对象,经过一些一致性检查后,我们“注册”了他们提供的定义。但是,如果已经存在具有相同密钥的定义,则不能注册新的定义,而是将其引用更新为该密钥的“官方”definition
我们希望这是严格的线程安全,但一个病态的场景出现在脑海中。假设客户端(使用我们的库)以非线程安全的方式共享引用,例如使用静态成员而不是局部变量:
private static Definition riskyReference = null;
如果一个线程设置了riskyReference=新定义(“键1”)
,填写定义,并调用我们的定义.Register(ref riskyReference)另一个线程也决定设置riskyReference=新定义(“键2”)代码>我们保证在我们的登记方法<代码> NeXealthy< /Cord>引用中,我们将不会被其他线程修改(因为对象引用被复制并在我们返回时被复制),或者在执行过程中,其他线程可以替换我们的对象。(如果我们引用的是指向原始存储位置的指针???),从而破坏了我们的健全性检查
请注意,这与对底层对象本身的更改不同,对于引用类型(类),这当然是可能的,但可以通过该类内的适当锁定轻松防范。但是,我们无法防范对外部客户端变量空间本身的更改!我们必须在方法顶部创建自己的参数副本,并覆盖底部的参数(例如),但考虑到处理不安全引用的疯狂性,这似乎对编译器来说更有意义
因此,我倾向于认为编译器可以复制引用,这样方法就可以处理对原始对象的一致引用(直到它在需要时更改自己的引用)不管其他线程上的原始位置会发生什么,但是在文档化和ref参数讨论中,我们很难找到关于这一点的明确答案
有人能用权威性的引用来缓解我的担忧吗
编辑结论:
通过一个多线程代码示例(谢谢Marc!)证实了这一点,并进一步思考了它,这是有道理的,我担心的确实是非自动线程安全行为通过引用传递大型结构而不是复制它们。另一个原因是,您可能希望设置对变量的长期监视,并需要传递对该变量的引用,该引用将看到变量的更改(例如,在null和活动对象之间更改),而自动复制/复制不允许这样做
因此,为了使我们的Register
方法对客户端疯狂行为具有鲁棒性,我们可以像以下那样实现它:
public static void Register(ref Definition newDefinition) {
Definition theDefinition = newDefinition; // Copy in.
//... Sanity checks, actual work...
//...possibly changing theDefinition to a new Definition instance...
newDefinition = theDefinition; // Copy out.
}
他们最终还是会有自己的线程问题,但至少他们的疯狂不会破坏我们自己的健全性检查过程,也不会让我们的检查错过一个糟糕的状态。当你使用ref
时,你传递的是调用方字段/变量的地址。因此,是的:两个线程可以在e字段/变量-但前提是他们都在与该字段/变量对话。如果他们对同一实例有不同的字段/变量,则情况正常(假设它是不可变的)
例如,在下面的代码中,Register
确实看到了Mutate
对变量所做的更改(每个对象实例实际上是不可变的)
使用系统;
使用系统线程;
福班{
公共字符串条{get;private set;}
公共Foo(字符串条){bar=bar;}
}
静态类程序{
静态Foo-Foo=新Foo(“abc”);
静态void Main(){
新线程(()=>{
登记册(ref foo);
}).Start();
对于(int i=0;i<20;i++){
突变(ref-foo);
睡眠(100);
}
Console.ReadLine();
}
静态空变(参考Foo obj){
obj=新Foo(obj.Bar+”);
}
静态无效寄存器(参考Foo obj){
而(obj.Bar.Length<10){
控制台写入线(对象条);
睡眠(100);
}
}
}
不,它不是“复制入,复制出”。相反,变量本身被有效地传入。不是值,而是变量本身。在方法期间所做的更改对于查看同一变量的任何其他人都是可见的
您可以在不涉及任何线程的情况下看到这一点:
using System;
public class Test
{
static string foo;
static void Main(string[] args)
{
foo = "First";
ShowFoo();
ChangeValue(ref foo);
ShowFoo();
}
static void ShowFoo()
{
Console.WriteLine(foo);
}
static void ChangeValue(ref string x)
{
x = "Second";
ShowFoo();
}
}
这个函数的输出是First、Second、Second-在ChangeValue
中调用ShowFoo()
表明foo
的值已经改变,这正是您所关心的情况
解决方案
使定义
不可变(如果以前没有),并将方法签名更改为:
public static Definition Register(Definition newDefinition)
然后调用者可以替换他们的变量,如果他们愿意的话,但是你的缓存不会被
public static Definition Register(Definition newDefinition)
myDefinition = Register(myDefinition);