原文​ 永远不要使用​​__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); }
//编译,正确.

用​​__gshared​​​这不大会发生.没有​​转换​​​的调用​​properlyUse​​​可能更好,但我不信人们​​记得​​​在没有​​编译器​​​对他们​​大喊大叫​​时就使用该函数.

即使他们第一次做对了,随着​​时间​​​推移,他们也一定会失败.当​​简单的,错误的​​​代码编译时,它肯定会进入​​源文件​​​.
很难做到正确的​​​线程安全​​​.需要从​​编译器​​​中获得的所有帮助.​​__gshared​​​提供零帮助.​​shared​​​至少​​高亮​​了有趣的地方.