JS通过事件循环对并发进行建模。结果,没有比赛条件。那么,在程序的主要范围内,以下类型的安全操作的缺点是什么?
const m = new Map([["foo",true]]);
//...
m.set("bar",false);
即使我要清空m
也不会引起任何问题,因为依赖于m
的每个操作都应该考虑为空情况。
也许有人可以说明可变数据类型所伴随的典型问题。
我知道这个问题可能是基于观点的,因此,如果您认为不合适,请随时结束。
谢谢!
JS通过事件循环对并发进行建模。结果,没有比赛条件。那么,在程序的主要范围内,以下类型的安全操作的缺点是什么?
const m = new Map([["foo",true]]);
//...
m.set("bar",false);
即使我要清空m
也不会引起任何问题,因为依赖于m
的每个操作都应该考虑为空情况。
也许有人可以说明可变数据类型所伴随的典型问题。
我知道这个问题可能是基于观点的,因此,如果您认为不合适,请随时结束。
谢谢!
JS通过事件循环对并发进行建模。结果,没有比赛条件。
让我们停在那儿。您可能不会获得两个不同的 threads 来尝试同时访问相同的内存位置,但是您仍然可以让程序的并发部分异步访问可变状态,而忽略它们并不孤单的事实。这仍然是比赛条件。
一个简单的例子:
name
0 A
1 A
2 B
3 B
4 C
5 C
6 D
7 D
8 E
9 E
10 F
11 G
var clock = out.value = 0;
async function incrementSlowly() {
if (clock == 12)
clock = 0; // reset
await delay(1000);
clock++;
out.value = clock;
}
function delay(t) { return new Promise(resolve => setTimeout(resolve,t)); }
<output id="out"></output>
<button onclick="incrementSlowly()">Tick!</button>
的值永远不会大于12?尝试一下快速按下按钮时会发生什么。
clock
函数的多个调用独立运行,并且在错误的时间进行检查-在延迟期间,另一个实例可能已经重新增加了incrementSlowly
。
在此示例中,我使用了可变变量,但是使用可变数据结构是相同的。当有多个代理通过不同的方法访问结构时,这种情况并不总是那么明显。
使用不可变的数据结构会迫使您明确进行状态操作,显然clock
实际上两次访问状态。
[...]因为依赖
m
的每个操作都应该考虑空情况
给事物命名很难。假设事情 可能很危险。
我认为自己是一个务实的开发人员,有时必须进行突变。但是,您的工作是知道可能出什么问题,并对周围的人进行危险教育,以便他们做出明智的决定。
我曾经在代码审查期间指出此函数正在更改其参数:(大致看起来像这样)
function foo(bar) {
const baz = {...bar};
baz.a.b.c = 10;
return baz;
}
该函数的作者回复说:“不,我之前已经克隆了它。所以该函数是'pure'”。
如果我没有花时间和那个人坐下来,我们可能会遇到一个重大的生产问题。原来,他们正在改变应用程序状态,结果我们进行了几次误报测试。
对我来说,这是变异数据时可能发生的更糟糕的情况:混乱。
由突变引起的错误可能很难追溯。
我始终鼓励团队中的人员不要为“不可能的案例”编写代码,因为这经常导致代码混乱不堪,以至于“以防万一”检查会增加维护工作量,并破坏我们对代码的信心。
但是,如果您允许不受控制的访问数据,那么“不可能的情况”就在拐角处等待。
我已经证明人们确实在变异事物而没有意识到。当您的团队中有不同经验水平的人时,您可以做的最好的事情是:
也许不是您可能期望的“学术”答案,但我想我可以分享一些技巧。
,JS通过事件循环对并发进行建模。结果,没有比赛条件。
这并不完全详尽,您还可以通过跨多个child processes运行程序来获取javascript中的并发性,在这种情况下,具有多个能够变异相同内存引用的线程确实可能导致争用条件或死锁。是的,不变性是用来保证thread-safety的设计模式之一: [基本上]通过将共享数据强制为只读。
这是很好的article,解释了为什么以及如何在诸如Java之类的多线程环境中遇到竞争条件。
您是对的,以单线程语言对内存引用进行更改没有错,实际上,这是使用JavaScript进行很长时间的方式。不变性直到最近才得到发展。 Hillel Wayne还说明了如何完全消除并发性,从而有助于消除由可变性引起的痛苦。
但是我宁愿从不同的角度来解决这个问题:易变性代表了体系结构问题,它在所有编程语言或环境中都是横向的,无论是多线程还是单线程都不会非常重要。
通过考虑架构问题,很容易实现,可变性如何导致不可预测的软件。有什么能保证在某些条件下对象将处于确定状态吗?并不是的。多少个实体可以引起给定对象状态的改变?那些变化可以控制吗?并不是真的,请考虑一下主要范围内的变量...实际上,任何事物都会影响其状态,并且诸如“ 每个操作都应考虑[...] ”之类的假设是不安全的,而且容易出错。 / p>
因此,虽然可变性不一定是错误的,但不可变性是开发人员工具包中的另一个工具,并且掌握了它可以使您成为更好的开发人员。
不可变数据结构(不变的对象)仅支持一种操作:读取,这使您的程序的行为就像摩尔机:在特定的时间,程序将始终处于其可能的状态之一。
您的程序是一堆操作,可以始终对其进行计算和测量:
R.pipe(
R.toUpper,// You know the state of your program here
R.concat(R.__,'!!'),// or here
)('Hello World'); // or here
您还可以将其阶段之一与其返回值互换,并且仍然让程序按预期方式运行:
R.pipe(
R.always('HELLO WORLD'),R.concat(R.__,)('Hello World');
不可变性还使时间旅行变得容易,并且使测试变得非常容易,但是真正重要的是,它非常容易推断状态及其转换,因为您将每个值都视为原始值: user.set('name','Giuseppe')
与'Giuseppe'.toUpperCase()
没什么不同。
随着时间的推移,您的程序最终是一系列确定的快照:
-> 'Hello World' -> 'HELLO WORLD' -> 'HELLO WORLD!!'
t(0) ----------- t(1) ----------- t(2) ------------- t(n)
注意:尽管您拥有更多的中间值,但不变性也使性能提高,因为它使深度相等变得毫无意义。
const user = { name: 'Giuseppe' };
const equals = (given,expected) => given === expected;
const newUser = { ...user,name: 'Marco' };
console.log('are users equals:',equals(user,newUser));
您将需要一个deepEqual函数来获得具有可变性的相同结果...(有关更多信息,请访问redux website)