前言

  最近两天的 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. 如何用 js 实现一个 call 函数

    如何用 js 实现一个 call 函数 原理 实现方式 总结 refs https://developer.mozilla.org/en-US/docs/Web/JavaScript/Referenc ...

  2. PEP 8 & Style Guide

    PEP 8 & Style Guide Style Guide for Python Code https://www.python.org/dev/peps/pep-0008/ PEP Py ...

  3. scroll tabs

    scroll tabs https://github.com/NervJS/taro-ui/blob/dev/src/components/tabs/index.tsx https://github. ...

  4. vue render html string

    vue render html string shit element ui render string array relativeShowConvert(data) { // log(`data` ...

  5. [转]ubuntu系统重新分区、根目录扩容

    原文地址:https://blog.csdn.net/code_segment/article/details/79237500,转载主要方便随时查阅,如有版权要求,请及时联系. gparted是一款 ...

  6. Ubuntu 下安装Anaconda + 显卡驱动 + CUDA + CUDNN + 离线安装环境

    写来给自己备忘,并不是什么教程- .- 下载安装包 Anaconda:https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ 显卡驱动:https ...

  7. 按照阿里巴巴规范创建Java线程池

    前言 Executors Executors 是一个Java中的工具类.提供工厂方法来创建不同类型的线程池. 常用方法: 1.newSingleThreadExecutor   介绍:创建一个单线程的 ...

  8. EFCodeFirst 数据库连接

    EFCodeFirst 数据库连接 EFCodeFirst 数据库连接 1.NuGet安装实体命令 PM> Install-Package EntityFramework  2.数据库连接字符串 ...

  9. ISC BIND9 - 最详细、最认真的从零开始的BIND 9 服务讲解

    DNS and BIND 服务的搭建说明 目录 目录 DNS and BIND 服务的搭建说明 1. 背景 1.1 DNS 1.2 FQDN 1.3 BIND 1.4 本文中搭建模拟DNS服务网络虚拟 ...

  10. Docker镜像构建原理解析(不装docker也能构建镜像)

    在devops流程里面 构建镜像是一个非常重要的过程,一般构建镜像是写dockerfile文件然后通过docker client来构建的image. docker client 会先检查本地有没有im ...