前言

  最近两天的 web 前端开发中,早前的锁实现 (自旋锁) 出现了一些不合理的现象,所以有了这片随笔

  什么是协程锁?能点进这个博客的的你肯定是明白的,不明白的人根本搜不到我这随笔,不多做赘述。

一些个人认识和实现经验

  1. 可重入锁:协程由于没有像『线程』那样的变量隔离,即缺少『计数标识』的挂载位置(多线程中计数标识直接或间接挂载在线程对象上),未实现可重入锁之前,编码开发中应该避免嵌套相同锁调用,否则造成死锁,
    所以『可重入锁』暂时没有太好的实现思路,希望有大神可以指点迷津。
  2. 自旋锁:优点是通俗易懂,一行 while(lock = getLock( lockKey )) await sleep(20) 就可以实现,缺点是不能保证代码的顺序性,造成无法预知的 Bug,如 异步打开遮罩 -> 异步关闭遮罩 的执行顺序将不可控,弃用。
  3. 公平锁:保证 FIFO 策略,使协程对锁先取先得,解决自旋锁的缺陷。
  4. 归一锁:类似多线程中的双重检验锁,在多线程中多用于单例的初始化或赋值,在 Web 前端可将重复异步幂等操作消减归一。如『获取用户信息』多个模块都调用此异步函数,只接发出一个『获取用户信息』的请求,多余 1 个的所有协程将进入 Pending 状态,一起等待返回结果。
    ( 结合公平锁,实现顺序等待调用)
  5. 等侯锁:只允许一个协程进入操作,多余 1 的所有协程进入 Pending 状态,等待被调用。
    (结合公平锁,实现顺序等待调用)

  暂时想到这么多,欢迎补充。

设计

  1. 归一锁:asyncOne( asyncFunction:AsyncFunction, ...keys:any[] )
  2. 等侯锁:asyncWait( asyncFunction:AsyncFunction, ...keys:any[] )

实现

  1. 锁管理器 ( 核心 ):根据一组 keys ,提供 查、加、解 锁的能力。
    getPendings(keys) / addPendings(keys) / delPendings(keys)

    // Synchronized async function
    const NEXT = Symbol();
    const PREV = Symbol();
    type LevelsNode = { [NEXT]?: LevelsMap; pendings?: PendingPromise<any>[]; [PREV]?: LevelsNode; key: string };
    type LevelsMap = Map<any, LevelsNode>; function findSyncNode(syncMap: LevelsMap, keys: any[], creation: false): [LevelsMap | undefined, LevelsNode | undefined, PendingPromise<any>[] | undefined];
    function findSyncNode(syncMap: LevelsMap, keys: any[], creation: true): [LevelsMap, LevelsNode, PendingPromise<any>[]];
    function findSyncNode(syncMap: LevelsMap, keys: any[], creation: boolean) {
    let prntNode: LevelsNode | undefined;
    let map: LevelsMap | undefined = syncMap;
    for (let i = 0; !!map && i < keys.length - 1; i++) {
    let lastNode = prntNode;
    prntNode = map.get(keys[i]);
    if (!prntNode) {
    if (creation) {
    prntNode = { [NEXT]: new Map(), [PREV]: lastNode, key: keys[i] };
    map.set(keys[i], prntNode);
    map = prntNode[NEXT];
    } else {
    map = undefined;
    }
    } else if (!(map = prntNode[NEXT])) {
    if (creation) {
    prntNode[NEXT] = new Map();
    map = prntNode[NEXT];
    } else {
    if (i < keys.length - 2) prntNode = undefined;
    }
    }
    }
    let mapNode = map?.get(keys[keys.length - 1]);
    if (creation) {
    if (!map) {
    Throwable("Impossible Error: No Prev Map.");
    } else if (!mapNode) {
    map.set(keys[keys.length - 1], (mapNode = { pendings: [], [PREV]: prntNode, key: keys[keys.length - 1] }));
    } else if (!mapNode.pendings) {
    mapNode.pendings = [];
    }
    }
    return [map, mapNode, mapNode?.pendings];
    }
    const getPendings = (syncMap: LevelsMap, keys: any[]) => findSyncNode(syncMap, keys, false)[2];
    const addPendings = (syncMap: LevelsMap, keys: any[]) => findSyncNode(syncMap, keys, true)[2];
    const delPendings = (syncMap: LevelsMap, keys: any[]) => {
    const [finalMap, finalVal] = findSyncNode(syncMap, keys, false);
    if (!!finalMap && !!finalVal) {
    // delete pending
    delete finalVal.pendings;
    // delete above including self
    tryDeleteNodeAndAbove(syncMap, finalVal);
    }
    };
    const tryDeleteNodeAndAbove = (syncMap: LevelsMap, node?: LevelsNode) => {
    while (!!node) {
    const nextMap = node[NEXT];
    if (!node.pendings && (!nextMap || nextMap.size === 0)) {
    const nodeKey = node.key;
    node = node[PREV];
    const map = node?.[NEXT] || syncMap;
    map.delete(nodeKey);
    } else {
    break;
    }
    }
    };
  2. 归一锁算法:
    const asyncOneMap: LevelsMap = new Map<any, LevelsNode>();
    export const asyncOne = async <T>(call: () => Promise<T>, ...keys: any[]): Promise<T> => {
    let pendings = getPendings(asyncOneMap, keys);
    if (!!pendings) {
    return (pendings[pendings.length] = pendingResolve<T>());
    } else {
    pendings = addPendings(asyncOneMap, keys);
    try {
    const result = await call.call(null);
    pendings.forEach(p => setTimeout(() => p.resolve(result)));
    return result;
    } catch (e) {
    pendings.forEach(p => setTimeout(() => p.reject(e)));
    throw e;
    } finally {
    delPendings(asyncOneMap, keys);
    }
    }
    };
  3. 等侯锁算法:
    const asyncWaitMap: LevelsMap = new Map<any, LevelsNode>();
    export const asyncWait = async <T>(call: () => Promise<T>, ...keys: any[]) => {
    let pendings = getPendings(asyncWaitMap, keys);
    /*sleep*/ if (!!pendings) await (pendings[pendings.length] = pendingResolve<void>());
    /*continue-------------*/ else pendings = addPendings(asyncWaitMap, keys);
    try {
    return await call.call(null);
    } finally {
    const next4awaken = pendings.shift();
    /*awaken the next*/ if (next4awaken !== undefined) next4awaken.resolve();
    /*unlock-----------------------------------*/ else delPendings(asyncWaitMap, keys);
    }
    };
  4. 依赖项 ( 第二关键 ):pendingResolve<T>:  <T>()=>PendingPromise<T>
    返回一个永久 pending 状态的 Promise, 充当协程断点的角色,必要时才手动 resolve / reject。
    本文不多赘述,请参考我的另一篇随笔: Web 前端 - 浅谈外部手动控制 Promise 状态:PendingPromise<T>
  5. 依赖项 ( 轻微重要 ):aw: ( ms: number)=>Promise<void>
    类似于多线程语言中的 sleep( ms:number )
    本文不多赘述,请参考我的另一篇随笔: Web 前端 - 优雅地 Callback 转 Promise :aw

Web 前端 - 又不仅限于 Web 前端 - 协程锁问题的更多相关文章

  1. 在C++中使用golang的协程

    开源项目cpp_features提供了一个仿golang协程的stackful协程库. 可以在c++中使用golang的协程,大概语法是这样的: #include <iostream> v ...

  2. Stackful 协程库 libgo(单机100万协程)

    libgo 是一个使用 C++ 编写的协作式调度的stackful协程库, 同时也是一个强大的并行编程库. 设计之初是为高并发分布式Linux服务端程序开发提供底层框架支持,可以让链接进程序的同步的第 ...

  3. BAT 前端开发面经 —— 吐血总结 前端相关片段整理——持续更新 前端基础精简总结 Web Storage You don't know js

    BAT 前端开发面经 —— 吐血总结   目录 1. Tencent 2. 阿里 3. 百度 更好阅读,请移步这里 聊之前 最近暑期实习招聘已经开始,个人目前参加了阿里的内推及腾讯和百度的实习生招聘, ...

  4. [面试专题]前端需要知道的web安全知识

    前端需要知道的web安全知识 标签(空格分隔): 未分类 安全 [Doc] Crypto (加密) [Doc] TLS/SSL [Doc] HTTPS [Point] XSS [Point] CSRF ...

  5. Bootstrap 简洁、直观、强悍的前端开发框架,让web开发更迅速、简单。

    Bootstrap 简洁.直观.强悍的前端开发框架,让web开发更迅速.简单.

  6. Web前端开发大系概览 (前端开发技术栈)

    前言 互联网建立50多年了,网站开发技术日新月异,但web前端始终离不开浏览器,最终还是HTML+JavaScript+CSS这3个核心,围绕这3个核心而开发出来大量技术框架/解决方案. 我从2000 ...

  7. 系统架构:Web应用架构的新趋势---前端和后端分离的一点想法

    最近研究servlet,看书时候书里讲到了c/s架构到b/s架构的演变,讲servlet的书都很老了,现在的b/s架构已经不是几年前的b/s架构,其实b/s架构就是web应用开发,对于这样的架构我们现 ...

  8. 淘宝前端工程师:国内WEB前端开发十日谈

    一直想写这篇"十日谈",聊聊我对Web前端开发的体会,顺便解答下周围不少人的困惑和迷惘.我不打算聊太多技术,我想,通过技术的历练,得到的反思应当更重要. 我一直认为自己是" ...

  9. Bootstrap 是一个用于快速开发 Web 应用程序和网站的前端框架

    Bootstrap 是一个用于快速开发 Web 应用程序和网站的前端框架.Bootstrap 是基于 HTML.CSS.JAVASCRIPT 的. 历史 Bootstrap 是由 Twitter 的 ...

随机推荐

  1. free open movie API all in one

    free open movie API all in one movie API TMDb API The Movie Database https://www.themoviedb.org/docu ...

  2. css text-align-last & text-align

    css text-align-last & text-align css https://caniuse.com/mdn-css_properties_text-align-last http ...

  3. TypeScript 面试题汇总(2020 版)

    TypeScript 面试题汇总(2020 版) TypeScript 3.9 https://www.typescriptlang.org/zh/ TypeScript 4.0 RC https:/ ...

  4. leetcode solution cracked tutorial

    leetcode solution cracked tutorial problemset https://leetcode.com/problemset/all/ Top Interview Que ...

  5. App Store Connect

    App Store Connect https://developer.apple.com/support/app-store-connect/ https://developer.apple.com ...

  6. IM & RTC

    IM & RTC 即时通信(IM) & 实时通信(RTC) 场景 即时通信(可靠性高,延时高) 场景包括文字聊天.语音消息发送.文件传输.音视频播放等; 发短信 实时通信(可靠性低,延 ...

  7. Flutter: The getter 'futureDynamicType' was called on null.

    > flutter packages pub upgrade

  8. 苏黎世财经对话区块链专家,NGK如何利用时间价值实现自身的垂直扩张?

    近日,苏黎世财经日报联合法兰西金融等多家知名媒体,专访了NGK. 苏黎世财经日报专栏记者玛科尔德表示,随着NGK DeFi的明星代币BGV登上去中心化金融的舞台,它千倍的收益率让生态投资者趋之若鹜. ...

  9. 解决java POI导入Excel超时问题

    由于要导入大量数据,后台会耗费很长时间,导致超时. 本项目前端request.js中设定的超时时间为150s. const service = axios.create({ baseURL: base ...

  10. Same Origin Policy 浏览器同源策略详解

    同源策略 Same Origin Policy 日常开发中最常与网络打交道,那关于浏览器的同源策略和跨域相关的知识是该整理一下了. 首先需要明确的是,同源策略是浏览器的安全策略,由于存在这个策略,我们 ...