Async/Await JavaScript/TypeScript Node.js 中的控制流程

Async/Await control flow in JavaScript/TypeScript in Node.js

上下文:我正在尝试在 TypeScript 中实现一个基本的套接字池。我当前的实现只是一个套接字列表,这些套接字附加了一个“AVAILABLE/OCCUPIED”枚举(它可能是一个布尔值),它允许我有一个类似互斥的机制来确保每个套接字只有 sends/receives 一次一条消息。

我的理解:我知道Node.js处理“并行”操作的方式是“单线程异步”。

我的推断:对我来说,这意味着一次只有一个“控制指针”/“代码读取头”/“控制流位置”,因为只有一个线程。在我看来,当调用“等待”时,读数头只会跳转到代码中的其他地方,而我正在“等待”的承诺还无法解决。但我不确定是否确实如此。

我想知道的是:“单线程异步”是否确保除了调用“await”之外的任何其他时间确实没有控制流位置的跳转?或者是否有一些底层调度程序确实可能会在随机时刻在任务之间跳转,就像正常的多线程一样?

我的问题:所有这些问题,我是否需要一个纯粹的 mutex/compare-and-swap 机制来确保我的类似互斥的 AVAILABLE/OCCUPIED 字段设置正确?

考虑以下代码:

export enum TaskSocketStatus
{
    AVAILABLE,  //Alive and available
    OCCUPIED,   //Alive and running a task
}

export interface TaskSocket
{
    status:TaskSocketStatus;
    socket:CustomSocket;
}


export class Server //A gateway that acts like a client manager for an app needing to connect to another secure server
{
    private sockets:TaskSocket[];

    [...]

    private async Borrow_Socket():Promise<TaskSocket|null>
    {
        for (const socket of this.sockets)
        {
            if (!socket.socket.Is_Connected())
            {
                await this.Socket_Close(socket);
                continue;
            }
            if (socket.status === TaskSocketStatus.AVAILABLE)
            {
//This line is where things could go wrong if the control flow jumped to another task;
//ie, where I'd need a mutex or compare-and-swap before setting the status
                socket.status = TaskSocketStatus.OCCUPIED;
                return (socket);
            }
        }
        if (this.sockets.length < this.max_sockets)
        {
            const maybe_socket = await this.Socket_Create();
            if (maybe_socket.isError())
            {
                return null;
            }
 //Probably here as well
            maybe_socket.value.status = TaskSocketStatus.OCCUPIED;
            return maybe_socket.value;
        }
        return null;
    }

    [...]
 }

我要避免的问题是两个不同的“SendMessage”任务由于竞争条件而借用同一个套接字。也许这是不必要的担心,但我想确定一下,因为这是一个潜在的问题,我 真的 不想在服务器已经投入生产时不得不面对它。 .

感谢您的帮助!

因此,在调用 await 时,不会流向另一个操作的控制流。当 运行ning 的 Javascript return 返回到事件循环,然后事件循环可以为下一个等待事件提供服务。已解决的承诺也通过事件循环工作(一个特殊的队列,但仍在事件循环中)。

因此,当您点击 await 时,不会立即将控制权转移到其他地方。它暂停该函数的进一步执行,然后使该函数立即 return 一个 promise 并继续控制 returned 到函数调用者的 promise。调用者的代码在收到该承诺后继续执行。只有当调用者或调用者的调用者或调用者的调用者的调用者(取决于调用堆栈的深度)returns 从任何事件开始返回事件循环时,整个执行链才执行事件循环有机会服务下一个事件并开始新的执行链。

一段时间后,当连接到原始 await 的底层异步操作完成时,它将向事件队列中插入一个事件。当其他 Javascript 执行 return 控制权回到事件循环并且此事件到达事件队列的开头时,它将被执行并解决 await 正在等待的承诺为了。只有这样 await 之后的函数内的代码才有机会 运行。当包含 awaitasync 函数最终完成它的内部执行时,最初 return 从 async 函数编辑的承诺在第一个 await was hit 将 resolve 并且调用者将收到通知,它返回的承诺已经解决(假设它在该承诺上使用了 await.then())。

所以,没有流量从一个地方跳到另一个地方。 Javascript 执行的当前线程 return 将控制权交还给事件循环(通过 return 调用和展开其调用堆栈),然后事件循环可以为下一个等待事件提供服务并启动新的执行链。只有当该执行链完成并且 returns 时,事件循环才能获取下一个事件并开始另一个执行链。这样,一次只有一个调用堆栈框架。

在你的代码中,我不太理解你所关心的。 Javascript中没有抢先切换。如果您的函数执行 await,那么它的执行将在那个时候暂停,其他代码可以 运行 在 promise 得到解决之前,它会在 await 之后继续执行。但是,没有先发制人的切换可以更改上下文和此线程中的 运行 其他代码,而无需您的代码调用一些异步操作然后继续完成回调或在 await.[=29 之后=]

因此,从纯 Javascript 的角度来看,不涉及异步操作的纯本地 Javascript 语句之间无需担心。这些保证是连续的和不间断的(我们假设您的代码中有 none 使用共享内存和工作线程 - 在您发布的代码中没有任何迹象)。

What I am wondering: does "single-threaded asynchrony" ensure that there is indeed no jump of the control flow position at any other time than when "await" is called ?

它确保在任何时候都不会跳转控制流位置,除非您 return 返回事件循环(展开调用堆栈)。它不会出现在 awaitawait 可能会导致您的函数 returning 并可能导致调用者然后 returning 回到事件循环,同时等待 returned 承诺解决,但是重要的是要了解控制流的变化仅在堆栈展开并且 return 控制权返回事件循环时发生,以便可以从事件队列中拉出并处理下一个事件。

Or is there some underlying scheduler that may indeed cause jumps between tasks at random moments, like normal multithreading ?

假设我们不是在谈论工作线程,nodejs 中没有先发制人的 Javascript 线程切换。只有当 Javascript return 的当前线程返回到事件循环时,对另一段 Javascript 的执行才会发生变化。

My question: All of this to ask, do I need a pure mutex/compare-and-swap mechanism to ensure that my mutex-like AVAILABLE/OCCUPIED field is set appropriately ?

不,您不需要互斥量。测试和设置之间没有 return 回到事件循环,因此可以保证它们不会被任何其他代码打断。