前言

  最近两天的 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)

    1. // Synchronized async function
    2. const NEXT = Symbol();
    3. const PREV = Symbol();
    4. type LevelsNode = { [NEXT]?: LevelsMap; pendings?: PendingPromise<any>[]; [PREV]?: LevelsNode; key: string };
    5. type LevelsMap = Map<any, LevelsNode>;
    6.  
    7. function findSyncNode(syncMap: LevelsMap, keys: any[], creation: false): [LevelsMap | undefined, LevelsNode | undefined, PendingPromise<any>[] | undefined];
    8. function findSyncNode(syncMap: LevelsMap, keys: any[], creation: true): [LevelsMap, LevelsNode, PendingPromise<any>[]];
    9. function findSyncNode(syncMap: LevelsMap, keys: any[], creation: boolean) {
    10. let prntNode: LevelsNode | undefined;
    11. let map: LevelsMap | undefined = syncMap;
    12. for (let i = 0; !!map && i < keys.length - 1; i++) {
    13. let lastNode = prntNode;
    14. prntNode = map.get(keys[i]);
    15. if (!prntNode) {
    16. if (creation) {
    17. prntNode = { [NEXT]: new Map(), [PREV]: lastNode, key: keys[i] };
    18. map.set(keys[i], prntNode);
    19. map = prntNode[NEXT];
    20. } else {
    21. map = undefined;
    22. }
    23. } else if (!(map = prntNode[NEXT])) {
    24. if (creation) {
    25. prntNode[NEXT] = new Map();
    26. map = prntNode[NEXT];
    27. } else {
    28. if (i < keys.length - 2) prntNode = undefined;
    29. }
    30. }
    31. }
    32. let mapNode = map?.get(keys[keys.length - 1]);
    33. if (creation) {
    34. if (!map) {
    35. Throwable("Impossible Error: No Prev Map.");
    36. } else if (!mapNode) {
    37. map.set(keys[keys.length - 1], (mapNode = { pendings: [], [PREV]: prntNode, key: keys[keys.length - 1] }));
    38. } else if (!mapNode.pendings) {
    39. mapNode.pendings = [];
    40. }
    41. }
    42. return [map, mapNode, mapNode?.pendings];
    43. }
    44. const getPendings = (syncMap: LevelsMap, keys: any[]) => findSyncNode(syncMap, keys, false)[2];
    45. const addPendings = (syncMap: LevelsMap, keys: any[]) => findSyncNode(syncMap, keys, true)[2];
    46. const delPendings = (syncMap: LevelsMap, keys: any[]) => {
    47. const [finalMap, finalVal] = findSyncNode(syncMap, keys, false);
    48. if (!!finalMap && !!finalVal) {
    49. // delete pending
    50. delete finalVal.pendings;
    51. // delete above including self
    52. tryDeleteNodeAndAbove(syncMap, finalVal);
    53. }
    54. };
    55. const tryDeleteNodeAndAbove = (syncMap: LevelsMap, node?: LevelsNode) => {
    56. while (!!node) {
    57. const nextMap = node[NEXT];
    58. if (!node.pendings && (!nextMap || nextMap.size === 0)) {
    59. const nodeKey = node.key;
    60. node = node[PREV];
    61. const map = node?.[NEXT] || syncMap;
    62. map.delete(nodeKey);
    63. } else {
    64. break;
    65. }
    66. }
    67. };
  2. 归一锁算法:
    1. const asyncOneMap: LevelsMap = new Map<any, LevelsNode>();
    2. export const asyncOne = async <T>(call: () => Promise<T>, ...keys: any[]): Promise<T> => {
    3. let pendings = getPendings(asyncOneMap, keys);
    4. if (!!pendings) {
    5. return (pendings[pendings.length] = pendingResolve<T>());
    6. } else {
    7. pendings = addPendings(asyncOneMap, keys);
    8. try {
    9. const result = await call.call(null);
    10. pendings.forEach(p => setTimeout(() => p.resolve(result)));
    11. return result;
    12. } catch (e) {
    13. pendings.forEach(p => setTimeout(() => p.reject(e)));
    14. throw e;
    15. } finally {
    16. delPendings(asyncOneMap, keys);
    17. }
    18. }
    19. };
  3. 等侯锁算法:
    1. const asyncWaitMap: LevelsMap = new Map<any, LevelsNode>();
    2. export const asyncWait = async <T>(call: () => Promise<T>, ...keys: any[]) => {
    3. let pendings = getPendings(asyncWaitMap, keys);
    4. /*sleep*/ if (!!pendings) await (pendings[pendings.length] = pendingResolve<void>());
    5. /*continue-------------*/ else pendings = addPendings(asyncWaitMap, keys);
    6. try {
    7. return await call.call(null);
    8. } finally {
    9. const next4awaken = pendings.shift();
    10. /*awaken the next*/ if (next4awaken !== undefined) next4awaken.resolve();
    11. /*unlock-----------------------------------*/ else delPendings(asyncWaitMap, keys);
    12. }
    13. };
  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. ES5 function & ES6 class & method type

    ES5 function & ES6 class & method type ES5 function "use strict"; /** * * @author ...

  2. vue & vue router & dynamic router

    vue & vue router & dynamic router https://router.vuejs.org/guide/essentials/dynamic-matching ...

  3. js & sort array object

    js & sort array object sort array object in js https://flaviocopes.com/how-to-sort-array-of-obje ...

  4. JULLIAN MURPHY:拥有良好的心态,运气福气便会自来

    JULLIAN MURPHY是星盟全球投资公司的基金预审经理,负责星盟投资项目预审,有着资深的基金管理经验,并且在区块链应用的兴起中投资了多个应用区块链技术的公司. JULLIAN MURPHY认为往 ...

  5. NGK公链账本技术浅析

    NGK公链账本是一个去中心化加密账本,运行在分布式网络上.分布式账本是区块链技术中最重要的组成部分之一.NGK作为公链资产,在公链中起到桥梁作用,可以促进其他资产(法币.数字资产.股权以及实物资产)交 ...

  6. Java基础篇(04):日期与时间API用法详解

    本文源码:GitHub·点这里 || GitEE·点这里 一.时间和日期 在系统开发中,日期与时间作为重要的业务因素,起到十分关键的作用,例如同一个时间节点下的数据生成,基于时间范围的各种数据统计和分 ...

  7. 纯js日历插件

    成品的效果图 1.HTML文件 <!DOCTYPE html> <html lang="en"> <head> <meta charset ...

  8. SSL/TLS协议详解(上):密码套件,哈希,加密,密钥交换算法

    本文转载自SSL/TLS协议详解(上):密码套件,哈希,加密,密钥交换算法 导语 作为一名安全爱好者,我一向很喜欢SSL(目前是TLS)的运作原理.理解这个复杂协议的基本原理花了我好几天的时间,但只要 ...

  9. 主键策略+mybayisPlus自动增长

    主键策略: 1.自动增长 有一点小缺陷:例如当一张表里的数据过于庞大时我们会进行分表操作,若是用自动增长策略,那么除了第一张表外的每一张表都必须知道上一张的表的的最后ID值.这个操作便会造成效率的变低 ...

  10. 还原Oracle数据库dmp文件(Win系统)

    准备工作: 1.核对数据字符集:   一般Oracle在安装的时候默认是选择ZHS16GBK,如有改动,使用 select userenv('language') from dual;语句查看使用的字 ...