您需要确定性的测试,并且如果互斥锁中断,其行为将发生变化。
以下示例是一个原子计数器问题。产生了两个工作人员,每个工作人员都循环执行三件事:
- 从全局计数器中获取值并将其存储在本地变量中
- 增加局部变量中的值
- 将局部变量写回全局计数器
至关重要的是,我在这里使用await
和setTimeout
在执行中创建中断。没有任何async
的{{1}}函数将是完全原子的,因此我们需要创建一些中断以允许调度程序在任务之间切换。如果互斥锁已损坏,则这些await
将允许调度程序从其他工作程序运行代码,因为每个await
都是Javascript调度程序更改作业的机会。
如果互斥体正常工作,您应该看到以下内容。在每个步骤之间,工作人员进入睡眠状态并醒来,但互斥体不允许其他工作人员做任何事情:
- 工人1获取锁
- 工作者1从全局计数器中读取值
await
- 工作者1将值从
0
递增到0
- 工作人员1将值
1
写回到全局计数器
- 工人1释放锁
- 工人2获取锁
- 工作者2从全局计数器中读取值
1
- 工作者2将值从
1
递增到1
- 工作者2将值
2
写回到全局计数器
- Worker 2解除锁定
结果:2
,预期值:2
如果互斥锁不起作用(或不使用),则两个工作程序将相互跳闸,最终结果将不正确。和以前一样,工人每次执行操作时都要睡觉:
- 工作者1从全局计数器中读取值
2
- 工作者2从全局计数器中读取值
0
- 工作者1将值从
0
递增到0
- 工作者2将值从
1
递增到0
- 工作人员1将值
1
写回到全局计数器
- 工作者2将值
1
写回到全局计数器
结果:1
,预期值:1
在两种情况下,两个工作人员都执行相同的操作,但是如果不执行命令,则结果不正确。
此示例是人为设计的,但可重复且主要是确定性的。当互斥锁工作时,您将始终获得相同的最终结果。否则,您总是会得到错误的结果。
工作演示:
2
var state = {
isMutexBroken: false,counter: 0,worker1LastAction: '',worker2LastAction: '',worker1IsActive: false,worker2IsActive: false,}
class Mutex {
constructor() {
this.current = Promise.resolve();
}
async acquire() {
if (state.isMutexBroken) {
return () => {};
}
let release;
const next = new Promise(resolve => {
release = () => {
resolve();
};
});
const waiter = this.current.then(() => release);
this.current = next;
return await waiter;
}
}
var mutex = new Mutex();
const renderState = () => {
document.getElementById('mutex-status').textContent = state.isMutexBroken ? 'Mutex is *not* working correctly. Press "fix mutex" to fix it.' : 'Mutex is working correctly. Press "break mutex" to break it.';
document.getElementById('counter').textContent = `Counter value: ${state.counter}`;
document.getElementById('worker1').textContent = `Worker 1 - last action: ${state.worker1LastAction}`;
document.getElementById('worker2').textContent = `Worker 2 - last action: ${state.worker2LastAction}`;
document.getElementById('start-test').disabled = state.worker1IsActive || state.worker2IsActive;
document.getElementById('break-mutex').disabled = state.worker1IsActive || state.worker2IsActive;
document.getElementById('fix-mutex').disabled = state.worker1IsActive || state.worker2IsActive;
}
// https://stackoverflow.com/a/39914235/8166701
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve,ms));
}
const worker = async(delay,count,id) => {
state[`${id}IsActive`] = true;
let workerCopyOfCounter;
for (let i = 0; i < count; i++) {
const unlock = await mutex.acquire();
state[`${id}LastAction`] = `Aquired lock.`;
renderState();
await sleep(delay);
workerCopyOfCounter = state.counter;
state[`${id}LastAction`] = `Acquired global counter: ${workerCopyOfCounter}`;
renderState();
await sleep(delay);
workerCopyOfCounter++;
state[`${id}LastAction`] = `Incremented counter: ${workerCopyOfCounter}`;
renderState();
await sleep(delay);
state.counter = workerCopyOfCounter;
state[`${id}LastAction`] = `Wrote ${workerCopyOfCounter} back to global counter.`;
renderState();
await sleep(delay);
unlock();
state[`${id}LastAction`] = `Released lock.`;
renderState();
await sleep(delay);
}
state[`${id}LastAction`] = `Finished.`;
state[`${id}IsActive`] = false;
renderState();
}
document.getElementById('break-mutex').onclick = () => {
state.isMutexBroken = true;
renderState();
}
document.getElementById('fix-mutex').onclick = () => {
state.isMutexBroken = false;
renderState();
}
document.getElementById('start-test').onclick = () => {
document.getElementById('test-result').textContent = '';
document.getElementById('start-test').textContent = 'Reset and start test';
state.counter = 0;
state.worker1LastAction = '';
state.worker2LastAction = '';
renderState();
const slow = document.getElementById('slow').checked;
const multiplier = slow ? 10 : 1;
Promise.all([
worker(20 * multiplier,10,'worker1'),worker(55 * multiplier,5,'worker2')
]).then(() => {
const elem = document.getElementById('test-result');
elem.classList.remove('pass');
elem.classList.remove('fail');
elem.classList.add(state.counter === 15 ? 'pass' : 'fail');
elem.textContent = state.counter === 15 ? 'Test passed' : 'Test failed';
});
}
renderState();
.flex-column {
display: flex;
flex-direction: column;
}
.flex-row {
display: flex;
}
.top-padding {
padding-top: 8px;
}
.worker-state-container {
background-color: #0001;
margin-top: 8px;
padding: 5px;
}
.pass {
background-color: limegreen;
color: white;
}
.fail {
background-color: red;
color: white;
}
最低版本:
<div class="flex-column">
<div className="flex-row">
<button id="break-mutex">Break mutex</button>
<button id="fix-mutex">Fix mutex</button>
<div id="mutex-status"></div>
</div>
<div className="flex-row">
<input type="checkbox" id="slow" name="slow"><label for="slow">slow</label>
</div>
<div class="flex-row top-padding">
<button id="start-test">Start test</button>
</div>
<div id="counter"></div>
<div>Expected end value: 15</div>
<div id="test-result"></div>
<div class="top-padding">
<div id="worker1" class="worker-state-container">
</div>
<div id="worker2" class="worker-state-container">
</div>
</div>
</div>
,
我将创建一个可能将对象按顺序放在共享数组上的实现。您可以模拟使用互斥锁按顺序访问数组。您的测试只需声明正确的插入顺序即可。我会利用setTimeout安排尝试锁定互斥锁并成功获取互斥锁的时间,然后将元素添加到共享数组。
本文链接:https://www.f2er.com/1591145.html