前言

  最近两天的 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. ES6 进制字面量 All In One

    ES6 进制字面量 All In One 二进制 & 八进制 & 字面量 https://developer.mozilla.org/en-US/docs/Web/JavaScript ...

  2. cloud linux cli & dart pub

    cloud linux cli & dart pub https://bcb8313e-05bf-4a93-9309-5f1f68eef1b1.ws-us02.gitpod.io/ https ...

  3. Vue & mobile UI components

    Vue & mobile UI components https://github.com/vuejs/awesome-vue https://awesome-vue.js.org/ http ...

  4. how to recursively all files in a folder with sudo permissions in macOS

    how to recursively all files in a folder with sudo permissions in macOS write bug OK sudo chmod 777 ...

  5. 数据库分表自增ID问题

    .................................................................................................... ...

  6. mybatisPlus中的模糊查询问题

    不能查询中文 记得在数据库的配置中写明编码格式characterEncoding=utf-8

  7. DOM的理解

    https://www.cnblogs.com/djtang/p/11538420.html  dom的理解 https://blog.csdn.net/jiuqiyuliang/article/de ...

  8. docker封装Spring Cloud(单机版)

    一.概述 微服务统一在一个git项目里面,项目的大致结构如下: ./ ├── auth-server │ ├── pom.xml │ └── src ├── common │ ├── pom.xml ...

  9. 00.从0实现一个JVM语言系列

    00.一个JVM语言的诞生 由于方才才获悉博客园文章默认不放在首页的, 原创文章主要通过随笔显示, 所以将文章迁移到随笔; 这篇帖子将后续更新, 欢迎关注! 这段时间要忙着春招实习, 所以项目更新会慢 ...

  10. 剑指 Offer 61. 扑克牌中的顺子 + 简单题 + 思维

    剑指 Offer 61. 扑克牌中的顺子 Offer_61 题目描述 java代码 package com.walegarrett.offer; /** * @Author WaleGarrett * ...