多线程笔试题 | 青训营笔记

237 阅读2分钟

[多线程笔试题 | 青训营笔记]

这是在第5次课《客户端容器》上课看到的一道笔试题,当时上课不是很理解,所以下课后去搜集了些资料辅助理解。

题目

以下代码在浏览器中的输出顺序、内容

<script>
    const now = Date.now();
    setTimeout(() => {
        console.log('time10', Date.now() - now);
    }, 10);
    setTimeout(() => {
        console.log('time30', Date.now() - now);
    }, 30);
    while (true) {
        if (Date.now() - now >= 20) {
            break;
        }
    }
    console.log(Date.now() - now);
</script>

在Microsoft Edge浏览器的输出结果是:

image.png

为什么输出结果不是

time10 10
20
time30 30

原因分析

这是因为JavaScript是单线程语言,这意味着JavaScript在任何时候只能执行一个任务。

image.png

程序按照顺序执行下来setTimeout() 函数会先被调用,然后才会执行 while 循环,按照这个说法,那么执行结果不就是上面这个吗?

这是由于setTimeout()函数它会在指定的时间后将回调函数添加到任务队列中,等待执行,但是如果在js在执行其他任务,回调函数就必须等待当前任务执行完成。

setTimeout() 函数在等待期间不会阻塞代码的执行,因此在setTimeout()等待期间,代码按顺序执行,到while循环中,由于只有满足了Date.now() - now >= 20条件才break中断循环,while会一直占用js任务,这时10ms后回调函数必须继续等待while循环任务完成。第二个setTimeout()函数等待时间为30ms,while占用时间为20ms左右,因此不会影响。

setTimeout 函数会在指定的时间后执行回调函数,但这个时间并不是精确的。实际上,setTimeout 的回调函数可能会在指定时间后的某个时间点执行。因此时间不是精确的20ms或者30ms。

解决方案

要避免 while 循环阻止 setTimeout() 的回调函数执行,可以使用异步编程技术

以下是使用Promise的例子:

<script>
    const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

    (async () => {
        const now = Date.now();
        setTimeout(() => {
            console.log('time10', Date.now() - now);
        }, 10);
        setTimeout(() => {
            console.log('time30', Date.now() - now);
        }, 30);
        await wait(20);
        console.log(Date.now() - now);
    })();
</script>

输出结果(Microsoft Edge): image.png

在这段代码中,定义了一个 wait 函数,它返回一个 Promise 对象,这个 Promise 对象会在指定的时间后解决。

然后,使用 async/await 语法来等待 Promise 对象解决,当执行到 await wait(20) 时,代码会暂停执行,等待 20 毫秒后继续执行。在此期间,其他代码(包括 setTimeout() 的回调函数)可以正常执行。

因此,在这段代码中,setTimeout() 的回调函数不会被阻塞,它们可以在指定时间后正常执行。