原文 永远不要使用
__gshared
.这是明显安全漏洞.改为使用
shared
.
如果使用时遇见
shared
的
编译
错误,那是
编译器
在警告你.应该认真
考虑
线程安全,然后才在正确的位置抛弃
shared
.
使用
__gshared
,编译器假装没有看到
变量
是
共享
的.并除非仔细思考,保证会生成
竞争
.
顺便,有优先
__gshared
而非
shared
的情况吗?似乎许多新手在用
__gshared
.
我
不完全
同意该点,这仅取决于你用法.一般,你应该使用
shared
,但
__gshared
有意义.只是在
多线程
可更改时才有问题,但如果它仅从
单线程
更改但从
多线程
读取,则一般不是问题.
总之:
单写/单读?用
__gshared
单写/多读?用
__gshared
多写/单读?用
shared
多写/多读?用
shared
如果与C对接,则需要
__gshared
.但是,是的,这里应该使用
shared
.
__gshared
表现不错,但我优先用
std.concurrency
,示例:
import std.stdio;
import std.concurrency;
import core.thread;
struct Result {
int value;
}
struct Done {
}
void run()
{
bool done = false;
while (!done) {
writeln("运行子线程");
receiveTimeout(1.seconds,
(Done msg) {
done = true;
});
}
// `发送`结果给所有者
// 假定,线程在上面循环中产生结果
ownerTid.send(Result(42));
}
void main()
{
auto worker = spawn(&run);
Thread.sleep(5.seconds);
worker.send(Done());
auto result = receiveOnly!Result();
writeln("结果:", result);
}
所有
这些
都可能是
竞争条件
.
这是
单写单读
的:
align(64) static struct S
{
align(1):
ubyte[60] off;
ulong x = 0;
}
__gshared S s;
void main()
{
import core.thread:;
import std.conv:;
new Thread(() {
foreach (i; 0 .. uint.max)
{
s.x = 0;
s.x = -1;
}
}).start();
foreach (i; 0 .. uint.max)
{
auto x = s.x;
assert(x == 0 || x == -1, to!string(x, 16));
}
}
如果你知道如何
安全
地访问
变量
,则可用
shared
.我坚持:永远不要使用
__gshared
.
快速测试表明
extern(C) extern shared
工作很好.
据我所知,
__gshared
仅在,你想在
单线程
程序中访问
共享C变量
时才工作良好.然后,如果稍后用
多线程
,你仍然会
失败
.
所以,永远不要(在
多线程
代码中)使用
__gshared
.
C没有
共享
概念,所以不是正确
类型
.加上
shared
只是
假
,并会导致麻烦.最好
明确
说明.
并不是说你应该自由使用
__gshared
,或只在
D中
使用.而是说
永远不应
使用它,这是错误的.
废话.在共享变量上加
shared
不是"说谎".
C
是否区分并不重要.但
D
重要.
如果你可识别出
__gshared
的
有效用例
,并用它
编写
正确代码,则你就知道
什么
时候不听我的.
其他人,
永远
不要使用
__gshared
.
__gshared
和
-boundscheck=off
一样糟糕.它们都是明显的
安全漏洞
.
auto x=s.x;
你的问题在此,而不是因为它是
__gshared
.
你复制了该值,显然可同时更改它,这是常识.
你不应这样使用它.而应该直接访问
s.x
.
而用
共享
,如果
读取线程
时先锁定,则结果相同,且在
更改前
会读取并处理该值.
读
x
时,就改了
x
.
auto x = s.x;
assert(s.x == 0 || s.x == -1, to!string(s.x, 16));
//多个竞争替代原来的1个竞争,且不能定位问题
shared
并不能解决条件竞争,这是对的.如果没有
-preview=nosharedaccess
,则无区别.所以不妨使用
shared
😉.
但是有了
-preview=nosharedaccess
,代码不再编译,你
不得不
考虑如何
安全
访问
共享数据
.哪个好?
所以:永远不要使用
__gshared
,总是用
-preview=nosharedaccess
.
如果你有
更好
抽象且仔细
锁定
访问数据,但
C不行
,如果想访问
C全局变量
,应使用
__gshared
,因为这就是它的
用途
.使用
shared
,帮不了你.
使用
__gshared
来共享数据
给C
,与使用
-boundscheck=on
发送数组到无此类限制的
C中
一样安全.
这里
结论
真的应该是,不要使用
C
.
不,使用
shared
确实可以帮你.
C没有
shared
限定符,但
C程序员
仍然必须考虑
线程安全
.调用
C函数
或访问
C全局变量
必须考虑
多线程
.
shared加上(-preview=nosharedaccess)
迫使你考虑
合同
是什么.
__gshared
没有.
不,这不对.
C
总是
不安全
的,这是正确的,但不重要.关键是你在
D端
可以/不能
做什么
.
-boundscheck=on
,不会轻易在
D端
搞砸.
C
端还是可以乱七八糟的.
-boundscheck=off
,
很容易
在D端搞砸.
shared
,不会轻易在D端搞砸.
C
端还是可以乱七八糟的.
__gshared
,很容易在
D端
搞砸.
shared
让你感觉
该语言
帮你预防问题.同样,对C,这是
假的
.
边界
在C和D中的定义相同,有
指针
和
大小
,你不能超过那个大小.是的,数据以不同的方式传达,但这很容易理解和使用.
shared
(加上
-preview=nosharedaccess
),
阻止
你上场.不会犯规.不能伤害
自己
.可通过
强制转换
告诉编译器
(1)
你确定要玩,及
(2)
,你将按C端的规则玩.
__gshared
只是让你在
球场
上奔跑.不知道规矩?编译器不在乎.
玩得开心
打断你的腿.
用
__gshared
:
extern(C) extern __gshared int x;
void fun() { x = 42; }
//编译,条件竞争
我
甚至
未发现我在做
危险
事情,因为第一次
天真
的通过了编译且似乎
工作正常
.
使用
shared
(加
-preview=nosharedaccess
):
extern(C) extern shared int x;
void fun() { x = 42; } /* 错误 */
如果查看文档,会发现关于
正确用法
.正如你所建议的,我想出了:
extern(C) extern shared int x;
void fun() { properlyUse(&x, 42); }
//仍错误,但共享.
我被迫
更多
地考虑
线程安全
.这里可以丢弃
shared
,因为调用了线程安全的
properlyUse
函数.所以:
extern(C) extern shared int x;
void fun() { properlyUse(cast(int*) &x, 42); }
//编译,正确.