Web 前端 - 又不仅限于 Web 前端 - 协程锁问题
前言
最近两天的 web 前端开发中,早前的锁实现 (自旋锁) 出现了一些不合理的现象,所以有了这片随笔
什么是协程锁?能点进这个博客的的你肯定是明白的,不明白的人根本搜不到我这随笔,不多做赘述。
一些个人认识和实现经验
- 可重入锁:协程由于没有像『线程』那样的变量隔离,即缺少『计数标识』的挂载位置(多线程中计数标识直接或间接挂载在线程对象上),未实现可重入锁之前,编码开发中应该避免嵌套相同锁调用,否则造成死锁,
所以『可重入锁』暂时没有太好的实现思路,希望有大神可以指点迷津。 - 自旋锁:优点是通俗易懂,一行 while(lock = getLock( lockKey )) await sleep(20) 就可以实现,缺点是不能保证代码的顺序性,造成无法预知的 Bug,如 异步打开遮罩 -> 异步关闭遮罩 的执行顺序将不可控,弃用。
- 公平锁:保证 FIFO 策略,使协程对锁先取先得,解决自旋锁的缺陷。
- 归一锁:类似多线程中的双重检验锁,在多线程中多用于单例的初始化或赋值,在 Web 前端可将重复异步幂等操作消减归一。如『获取用户信息』多个模块都调用此异步函数,只接发出一个『获取用户信息』的请求,多余 1 个的所有协程将进入 Pending 状态,一起等待返回结果。
( 结合公平锁,实现顺序等待调用) - 等侯锁:只允许一个协程进入操作,多余 1 的所有协程进入 Pending 状态,等待被调用。
(结合公平锁,实现顺序等待调用)
暂时想到这么多,欢迎补充。
设计
- 归一锁:asyncOne( asyncFunction:AsyncFunction, ...keys:any[] )
- 等侯锁:asyncWait( asyncFunction:AsyncFunction, ...keys:any[] )
实现
- 锁管理器 ( 核心 ):根据一组 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;
- }
- }
- };
- // Synchronized async function
- 归一锁算法:
- 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);
- }
- }
- };
- const asyncOneMap: LevelsMap = new Map<any, LevelsNode>();
- 等侯锁算法:
- 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);
- }
- };
- const asyncWaitMap: LevelsMap = new Map<any, LevelsNode>();
- 依赖项 ( 第二关键 ):pendingResolve<T>: <T>()=>PendingPromise<T>
返回一个永久 pending 状态的 Promise, 充当协程断点的角色,必要时才手动 resolve / reject。
本文不多赘述,请参考我的另一篇随笔: Web 前端 - 浅谈外部手动控制 Promise 状态:PendingPromise<T> - 依赖项 ( 轻微重要 ):aw: ( ms: number)=>Promise<void>
类似于多线程语言中的 sleep( ms:number )
本文不多赘述,请参考我的另一篇随笔: Web 前端 - 优雅地 Callback 转 Promise :aw
Web 前端 - 又不仅限于 Web 前端 - 协程锁问题的更多相关文章
- 在C++中使用golang的协程
开源项目cpp_features提供了一个仿golang协程的stackful协程库. 可以在c++中使用golang的协程,大概语法是这样的: #include <iostream> v ...
- Stackful 协程库 libgo(单机100万协程)
libgo 是一个使用 C++ 编写的协作式调度的stackful协程库, 同时也是一个强大的并行编程库. 设计之初是为高并发分布式Linux服务端程序开发提供底层框架支持,可以让链接进程序的同步的第 ...
- BAT 前端开发面经 —— 吐血总结 前端相关片段整理——持续更新 前端基础精简总结 Web Storage You don't know js
BAT 前端开发面经 —— 吐血总结 目录 1. Tencent 2. 阿里 3. 百度 更好阅读,请移步这里 聊之前 最近暑期实习招聘已经开始,个人目前参加了阿里的内推及腾讯和百度的实习生招聘, ...
- [面试专题]前端需要知道的web安全知识
前端需要知道的web安全知识 标签(空格分隔): 未分类 安全 [Doc] Crypto (加密) [Doc] TLS/SSL [Doc] HTTPS [Point] XSS [Point] CSRF ...
- Bootstrap 简洁、直观、强悍的前端开发框架,让web开发更迅速、简单。
Bootstrap 简洁.直观.强悍的前端开发框架,让web开发更迅速.简单.
- Web前端开发大系概览 (前端开发技术栈)
前言 互联网建立50多年了,网站开发技术日新月异,但web前端始终离不开浏览器,最终还是HTML+JavaScript+CSS这3个核心,围绕这3个核心而开发出来大量技术框架/解决方案. 我从2000 ...
- 系统架构:Web应用架构的新趋势---前端和后端分离的一点想法
最近研究servlet,看书时候书里讲到了c/s架构到b/s架构的演变,讲servlet的书都很老了,现在的b/s架构已经不是几年前的b/s架构,其实b/s架构就是web应用开发,对于这样的架构我们现 ...
- 淘宝前端工程师:国内WEB前端开发十日谈
一直想写这篇"十日谈",聊聊我对Web前端开发的体会,顺便解答下周围不少人的困惑和迷惘.我不打算聊太多技术,我想,通过技术的历练,得到的反思应当更重要. 我一直认为自己是" ...
- Bootstrap 是一个用于快速开发 Web 应用程序和网站的前端框架
Bootstrap 是一个用于快速开发 Web 应用程序和网站的前端框架.Bootstrap 是基于 HTML.CSS.JAVASCRIPT 的. 历史 Bootstrap 是由 Twitter 的 ...
随机推荐
- ES5 function & ES6 class & method type
ES5 function & ES6 class & method type ES5 function "use strict"; /** * * @author ...
- vue & vue router & dynamic router
vue & vue router & dynamic router https://router.vuejs.org/guide/essentials/dynamic-matching ...
- js & sort array object
js & sort array object sort array object in js https://flaviocopes.com/how-to-sort-array-of-obje ...
- JULLIAN MURPHY:拥有良好的心态,运气福气便会自来
JULLIAN MURPHY是星盟全球投资公司的基金预审经理,负责星盟投资项目预审,有着资深的基金管理经验,并且在区块链应用的兴起中投资了多个应用区块链技术的公司. JULLIAN MURPHY认为往 ...
- NGK公链账本技术浅析
NGK公链账本是一个去中心化加密账本,运行在分布式网络上.分布式账本是区块链技术中最重要的组成部分之一.NGK作为公链资产,在公链中起到桥梁作用,可以促进其他资产(法币.数字资产.股权以及实物资产)交 ...
- Java基础篇(04):日期与时间API用法详解
本文源码:GitHub·点这里 || GitEE·点这里 一.时间和日期 在系统开发中,日期与时间作为重要的业务因素,起到十分关键的作用,例如同一个时间节点下的数据生成,基于时间范围的各种数据统计和分 ...
- 纯js日历插件
成品的效果图 1.HTML文件 <!DOCTYPE html> <html lang="en"> <head> <meta charset ...
- SSL/TLS协议详解(上):密码套件,哈希,加密,密钥交换算法
本文转载自SSL/TLS协议详解(上):密码套件,哈希,加密,密钥交换算法 导语 作为一名安全爱好者,我一向很喜欢SSL(目前是TLS)的运作原理.理解这个复杂协议的基本原理花了我好几天的时间,但只要 ...
- 主键策略+mybayisPlus自动增长
主键策略: 1.自动增长 有一点小缺陷:例如当一张表里的数据过于庞大时我们会进行分表操作,若是用自动增长策略,那么除了第一张表外的每一张表都必须知道上一张的表的的最后ID值.这个操作便会造成效率的变低 ...
- 还原Oracle数据库dmp文件(Win系统)
准备工作: 1.核对数据字符集: 一般Oracle在安装的时候默认是选择ZHS16GBK,如有改动,使用 select userenv('language') from dual;语句查看使用的字 ...