惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Schneier on Security
A
Arctic Wolf
S
Security Affairs
O
OpenAI News
SecWiki News
SecWiki News
TaoSecurity Blog
TaoSecurity Blog
H
Heimdal Security Blog
T
Threat Research - Cisco Blogs
Hacker News: Ask HN
Hacker News: Ask HN
N
News | PayPal Newsroom
Google Online Security Blog
Google Online Security Blog
C
Cisco Blogs
The Hacker News
The Hacker News
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
C
CXSECURITY Database RSS Feed - CXSecurity.com
P
Privacy International News Feed
V
Vulnerabilities – Threatpost
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
T
Tenable Blog
T
The Exploit Database - CXSecurity.com
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Spread Privacy
Spread Privacy
人人都是产品经理
人人都是产品经理
www.infosecurity-magazine.com
www.infosecurity-magazine.com
V2EX - 技术
V2EX - 技术
L
LINUX DO - 最新话题
The GitHub Blog
The GitHub Blog
博客园 - 三生石上(FineUI控件)
T
The Blog of Author Tim Ferriss
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Visual Studio Blog
The Cloudflare Blog
N
News and Events Feed by Topic
量子位
Google DeepMind News
Google DeepMind News
Application and Cybersecurity Blog
Application and Cybersecurity Blog
L
LINUX DO - 热门话题
P
Palo Alto Networks Blog
Stack Overflow Blog
Stack Overflow Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Attack and Defense Labs
Attack and Defense Labs
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Hacker News - Newest:
Hacker News - Newest: "LLM"
Apple Machine Learning Research
Apple Machine Learning Research
The Register - Security
The Register - Security
Microsoft Security Blog
Microsoft Security Blog
Know Your Adversary
Know Your Adversary
Webroot Blog
Webroot Blog

重归混沌的BLOG

给silly实现了一个ernro模块 | 重归混沌的BLOG 给silly实现了一个ernro模块 | 重归混沌的BLOG 第一次在生产环境使用 Vibe Coding | 重归混沌的BLOG 第一次在生产环境使用 Vibe Coding | 重归混沌的BLOG API 设计的艰难抉择 | 重归混沌的BLOG API 设计的艰难抉择 | 重归混沌的BLOG 十年 | 重归混沌的BLOG 十年 | 重归混沌的BLOG 在Go语言中如何使XML加载内存无限趋近于0 | 重归混沌的BLOG 在Go语言中如何使XML加载内存无限趋近于0 | 重归混沌的BLOG 对跨服玩法中的分布式一致性问题进行简单抽象 | 重归混沌的BLOG 对跨服玩法中的分布式一致性问题进行简单抽象 | 重归混沌的BLOG Go语言逃逸分析之slice和map | 重归混沌的BLOG Go语言逃逸分析之slice和map | 重归混沌的BLOG 谈谈观测 | 重归混沌的BLOG 谈谈观测 | 重归混沌的BLOG 写了个AI Agent服务端 | 重归混沌的BLOG 写了个AI Agent服务端 | 重归混沌的BLOG 谈谈代码设计中“严丝合缝” | 重归混沌的BLOG 谈谈代码设计中“严丝合缝” | 重归混沌的BLOG 一次艰难的线上游戏服务器内存排查经历 | 重归混沌的BLOG 一次艰难的线上游戏服务器内存排查经历 如何基于LanguageServerProtocol来编写lint工具 谈谈游戏服务器中RPC模块的设计 谈谈游戏服务器代码抽象 最近碰到的一个分布式一致性问题 谈谈游戏服务器的自动化测试 对Raft协议的一点理解 使用mmap来学习/proc/pid/smaps 2023(完) 再次实现了一个Lua性能分析器 终于给Silly的定时器增加了取消功能 一次虚拟内存排查经历 游戏服务器分布式数据的一种同步的思路 2022(完) Go语言之闭包篇 一例误用unsafe包引起的内存问题 Go语言之内存篇 初识Go语言 重新抽象图形API 给Lua实现了一个数学库 谈谈跨平台图形API的抽象 寻路和Flocking算法的结合 行为树的一种高效实现 内测过程中Shader出现的问题 彻底解决多国语言 谈谈数据库的选型 再谈Lua热更新(终) 初窥Rust 关于游戏服务器的服务拆分 ECS的初步实现 ECS初探 屏幕空间(SreenSpace)的想象力 一些对辐射度量学的理解 深度缓冲和半透明渲染 Mysql的间隙锁 更新一些GPU相关知识 2020 地形渲染之爬过的坑 Lua5.3 GC源码阅读(5) 实现一个数据库存储队列 再学计算机图形学入门 再谈分布式服务架构 游戏上线一个月后的反思 一次并发Bug 双向链表的三种实现 谈谈随机数的使用 再谈性能优化 2019 Lua中的函数式编程 重构登录逻辑 Unity资源管理(续) 谈谈Unity的资源管理 一次关于Cache的性能分析 历史之2018 DC3算法 移动平台native代码遭遇的坑 从CPU层面谈谈优化 开卷有益(UNIX编程艺术篇) GC竞争问题 通过Mesh投影来实现贴花系统 谈谈我对数据同步的理解 又一个类型提升引起的Bug Lua5.3 GC源码阅读(4) Lua5.3 GC源码阅读(3) Lua5.3 GC源码阅读(2) Lua5.3 GC源码阅读(1) 三角形光栅化时遇到的坑 一次git事故 再见2017 又一个lua调试器 客户端缓存落地方案 Paxos算法 HTTP服务器的特点 一次性能优化经历 关于CPU分支预测 C程序中让两个不同版本的库共存 实现了一个AOI模块 一个高可伸缩的游戏服务器架构 关于网络协议封装的一些新想法
为silly增加了互斥锁
重归混沌 · 2023-04-30 · via 重归混沌的BLOG

silly是基于Lua语言的Coroutine机制而实现的一个高并发网络框架。

其coroutine调度机制很简单,所有的函数都被包裹在coroutine中被执行,当代码调用socket.read/core.sleep等指定API时,当前coroutine会挂起,直到socket.read/core.sleep返回结果为止。在此coroutine被挂起期间,会将CPU调度给其他coroutine来运行。

大部分情况下,基于coroutine编程和基于多线程编程的思路是一致的。

例外就是,由于是调用指定API时才会挂起当前coroutine,相比多线程而言,基于coroutine的调度机制会引入一个缺点和一个优点。

缺点就是,如果不慎编写死循环代码,当前coroutine永远也不会挂起,其他coroutine永远也得不到执行的机会。

优点刚好就是缺点的另一面,由于只在调用指定API才会挂起当前coroutine, 我们可以在coroutine中安心的修改所有数据而不必担心由于调度的不确定性而产生数据竞争问题。同样由于我们在需要挂起时才挂起,所有不必要的上下文切换都可以避免,可以极大地减少上下文切换的开销。


一直以来,我都认为mutex是为了解决抢占式调度的不确定性才被发明的。由于silly不存在抢占式调度,所以没有必要实现一个mutex锁。

但是就我这几年的经验来看,不管是不是抢占式调度,并发问题始终都存在。

只不过非抢占式调度所造成的影响范围更少,以致为我认为那不是并发的问题,是异步的问题(现在回头看看这两个概念也没有分得这么清楚)。

对于这种异步问题,我通常的做法,要么是做惰性队列,要么是做失败补偿。都是针对特定业务逻辑case by case的去实现。并没有一套成熟通用的方案。

下面来看两个例子(一个基于失败补偿,一个基于惰性队列):

---失败被偿
local user = {
    money = 100
}

func Foo() {
    if user.money < 50 then
        return false
    end
    user.money = user.money - 50
    ok = rpc:call("FooOtherService")
    if !ok {
        user.money = user.money + 50
    }
    return ok
}

func Bar() {
    if user.money < 60 then
        return false 
    end
    user.money = user.money - 60
    ok = rpc:call("BarOtherService")
    if !ok {
        user.money = user.money + 60
    }
    return ok
}
---惰性队列
local user = {
    money = 100,
    q = nil,
}

func enter() {
    if user.q then
        table.insert(user.q, core.running())
        core.wait()
    } else {
        user.q = {}
    }
}

func leave() {
    if user.q then
        co := table.remove(user.q, 1)
        if not co then
            user.q = nil
        else
            core.wakeup(co)
        end
    end
}

func Foo() {
    enter()
    if user.money < 50 then
        return false 
    end
    ok = rpc:call("FooOtherService")
    if ok {
        user.money = user.money - 50
    }
    leave()
    return ok
}

func Bar() {
    enter()
    if user.money < 60 then
        return false 
    end
    ok = rpc:call("BarOtherService")
    if ok {
        user.money = user.money - 60
    }
    leave()
    return ok
}

对比上面两段代码,可以发现,失败补偿逻辑基本没有任何被抽象的可能。

惰性队列虽然可以借助于Lua语言的动态性来抽象成库,但他只能解决单层异步问题,再复杂的异步问题惰性队列就无法完美抽象了。

还是上面的代码,假如Bar函数改为如下需求, 就需要新增一个惰性队列来处理。不然就会产生死锁。

local user = {
    money = 100,
    money2 = 100,
    q = nil,
    q2 = nil,
}

func enter(obj, name) {
    if obj[name] then
        table.insert(obj[name], core.running())
        core.wait()
    } else {
        obj[name] = {}
    }
}

func leave(obj, name) {
    if obj[name] then
        co := table.remove(obj[name], 1)
        if not co then
            obj[name] = nil
        else
            core.wakeup(co)
        end
    end
}

func Foo() {
    enter(user, "q")
    if user.money < 50 then
        return false 
    end
    ok = rpc:call("FooOtherService")
    if ok {
        user.money = user.money - 50
    }
    leave(user, "q")
    return ok
}

func Bar() {
    enter(user, "q2")
    if user.money2 < 60 then
        return false 
    end
    --do something
    ok := Foo()
    if ok {
        user.money2 = user.money2 - 60
    }
    leave(user, "q2")
}

最近突然觉得把这类问题归结于并发问题, 一把可重入锁就可以完美解决上面所有问题。

比如下面代码:

local user = {
    money = 100,
    money2 = 100,
    lock = lock:new()
}

func Foo() {
    local guad<close> = user.lock()
    if user.money < 50 then
        return fales
    end
    ok = rpc:call("FooOtherService")
    if ok {
        user.money = user.money - 50
    }
    return ok
}

func Bar() {
    local guad<close> = user.lock()
    if user.money2 < 60 then
        return false 
    end
    --do something
    ok := Foo()
    if ok {
        user.money2 = user.money2 - 60
    }
}

由于锁是可重入的,所以同一个coroutine内的函数者都可以加锁成功,这样不管后期这个模块怎么修改,模块内都不会产生死锁效果。

按照传统方式,一个锁在使用之前是需要new出来并初始化的,在一些极端场景(比如我之前的经历,有400W个格子)可能会需要new出来很多锁。

由于我们的非抢占式调度的机制,大部分情况下,锁冲突的概率很小,所有需要锁的地方都new一个锁,在这种场合下稍嫌浪费。

换句话说,虽然我们需要实现一把互斥锁,但是他是乐观的。

基于以上思路,我换了一种锁的实现方式,它的使用方式大概如下:

local mutex = require "sys.sync.mutex"
local user = {
    money = 100,
    money2 = 100,
}

func Foo() {
    local guad<close> = mutex.lock(user)
    if user.money < 50 then
        return fales
    end
    ok = rpc:call("FooOtherService")
    if ok {
        user.money = user.money - 50
    }
    return ok
}

func Bar() {
    local guad<close> = mutex.lock(user)
    if user.money2 < 60 then
        return false 
    end
    --do something
    ok := Foo()
    if ok {
        user.money2 = user.money2 - 60
    }
}

基于乐观锁的前提,这个锁被分配出来之后几乎不会发生碰撞,很快就会被释放掉。

我们在mutex内部可以做一些内存优化,比如我们做一个mutex cache, 当一把锁释放后就放入mutex cache中,当我们需要一个锁时先尝试从cache中分配等。

这样我们就有了一种通用的抽象方案。

ps. 写这篇文章时,我特意去查了wiki, 互斥锁原本就是为了解决并发问题而存在的,这种并发并不局限于抢占式并发还是非抢占式并发。