测试JavaScript互斥量实现

我已经为javascript /打字稿编写了互斥量实现,但是我在如何测试它的问题上苦苦挣扎。这是实现:

class Mutex {

    private current = Promise.resolve();

    async acquire() {
        let release: () => void;
        const next = new Promise<void>(resolve => {
            release = () => { resolve(); };
        });
        const waiter = this.current.then(() => release);
        this.current = next;
        return await waiter;
    }

}

用法:

const mut = new Mutex();

async function usesMutex() {
  const unlock = await mut.acquire();
  try {
    await doSomeStuff();
    await doOtherStuff();
  } finally {
    unlock();
  }
}

我不确定是否有任何简便的方法来创建这种时序问题,如果互斥体无法按预期工作,会导致测试失败。任何建议将不胜感激。

iCMS 回答:测试JavaScript互斥量实现

您需要确定性的测试,并且如果互斥锁中断,其行为将发生变化。

以下示例是一个原子计数器问题。产生了两个工作人员,每个工作人员都循环执行三件事:

  1. 从全局计数器中获取值并将其存储在本地变量中
  2. 增加局部变量中的值
  3. 将局部变量写回全局计数器

至关重要的是,我在这里使用awaitsetTimeout在执行中创建中断。没有任何async的{​​{1}}函数将是完全原子的,因此我们需要创建一些中断以允许调度程序在任务之间切换。如果互斥锁已损坏,则这些await将允许调度程序从其他工作程序运行代码,因为每个await都是Javascript调度程序更改作业的机会。

如果互斥体正常工作,您应该看到以下内容。在每个步骤之间,工作人员进入睡眠状态并醒来,但互斥体不允许其他工作人员做任何事情:

  1. 工人1获取锁
  2. 工作者1从全局计数器中读取值await
  3. 工作者1将值从0递增到0
  4. 工作人员1将值1写回到全局计数器
  5. 工人1释放锁
  6. 工人2获取锁
  7. 工作者2从全局计数器中读取值1
  8. 工作者2将值从1递增到1
  9. 工作者2将值2写回到全局计数器
  10. Worker 2解除锁定

结果:2,预期值:2

如果互斥锁不起作用(或不使用),则两个工作程序将相互跳闸,最终结果将不正确。和以前一样,工人每次执行操作时都要睡觉:

  1. 工作者1从全局计数器中读取值2
  2. 工作者2从全局计数器中读取值0
  3. 工作者1将值从0递增到0
  4. 工作者2将值从1递增到0
  5. 工作人员1将值1写回到全局计数器
  6. 工作者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

大家都在问