lua 如何实现 阻塞线程和非阻塞线程

坦白说,你的描述绝对正确。
当我第一次接触到这个东西的时候,我是这么理解的。
但后来我发现事情并没有那么简单。
例如,在Linux系统中,使用socket进行编程时,实际上可以通过将socket设置为非阻塞状态来实现非阻塞模式。
我记得我正在调用 fcntl 函数来更改 FIONBIO 标志。
有趣的是,这个东西和系统底层的调度有很大关系。
我之前在一个项目中就遇到过这个问题。
主线程发起了一个非阻塞写操作。
结果,由于内核繁忙,数据从未发送。
这时候如果主线程不断地在循环中重试,那么CPU的占用率就会不断增加,最终系统会直接帮你终止进程。
这是一个典型的“假非阻塞”陷阱。

当我们改变方案时,我们使用了像select或epoll这样的I/O复用机制。
说实话,这个东西写起来比直接用多线程要复杂一些,但是性能却好很多。
我心里有件事。
某金融系统经过ePoll优化后,单台服务器的并发连接数从数百个增加到数千个。
我记得数据在X左右,但我建议你查看最新版本的性能测试报告。
最重要的是,该模型允许您使用单个线程管理数百或数千个连接,并且资源开销极低。

但是说完这句话后,多线程实现非阻塞也有其缺点。
比如我之前维护过一个电商后端,使用多线程来处理异步任务。
结果,由于线程池配置不当,有时会导致主线程填满任务队列,新任务被挂起。
当时我们花了整整两周的时间才发现是某个第三方模块的调用逻辑有问题,导致整个线程池死锁。
因此需要明确的是,非阻塞模式并不是灵丹妙药。
即使使用不当,也会致命。

我还没有亲自运行协程来强制执行该领域的非阻塞,但我听说Python的asyncio现在已经得到了非常广泛的使用。
它们使用事件循环来暂停阻塞操作,而主线程继续执行其他任务。
我听说它非常有效,但是调试它就像看鬼图一样。
这个可能有点极端,但是我觉得对于复杂的业务场景,还是要根据实际需求来选择解决方案。

阻塞队列和线程池原理

哦,队列,我知道那件事。
让我告诉你我当时陷入的陷阱。

我从2 005 年开始开发Java,那时候没有线程池,并发都是手动完成的。
我曾经做过一个订购系统,需求是用户下单后要排队等待处理,不能插队。
我自己用数组创建了一个队列,后来发现线程运行速度太快,就跳进了队列,导致顺序乱了。
就在那时我意识到队列需要是线程安全的。

后来我用了ArrayBlockingQueue,一个容量为1 000的有限队列。
结果双十一的队列被订单挤满,Add方法直接抛出异常,导致系统崩溃。
后来改成了LinkedBlockingQueue,没有上限。
结果,内存直接爆炸了。
教训是队列大小需要正确设置,不能太大或太小。

后来,当我们忙于生产者-消费者工作时,就不再使用BlockingQueue了。
生产者添加一些东西,消费者取出一些东西,工作非常顺利。
然而,当我使用offer添加元素时,队列已满,并且立即返回false。
我没有注意,所以错过了几个订单,顾客抱怨了。
后来我改用put,然后就真的卡住了。
生产者在没有工作要做时等待,消费者在没有数据时等待,从而降低性能。
我这才意识到,方法是要看场景的。

这同样适用于线程池。
2 007 年,我创建了一个文件下载器并使用 Executors.newFixedThreadPool(1 0) 创建 1 0 个线程。
随着用户增多,下载任务堆积,线程池爆炸,系统卡住。
后来我自己用ThreadPoolExecutor创建了一个,创建了一个稳定的有界队列。
然而,我后来发现,在CPU密集型任务期间,线程已满,必须创建新线程,再次填满内存。
这时候我才意识到CPU密集型任务需要配备小线程池,IO密集型任务需要配备大线程池。

不要盲目使用执行器的这些静态方法。
当时我用 Executors.newCachedThreadPool() 创建了一个,但是由于队列无限,内存爆炸。
后来我切换到ThreadPoolExecutor并指定了队列大小,这起作用了。
但后来我发现,使用CallerRunsPolicy时,调用者线程需要自己完成任务,这有​​时不合理,影响性能。

运行线程池的问题是饱和策略。
有一次我使用了默认的AbortPolicy,但是任务提交太快,直接抛出了RejectedExecutionException,导致系统崩溃。
后来我改用CallerRunsPolicy。
结果调用线程自己执行了任务,CPU直接爆炸了。
我这才意识到,要根据场景选择策略,不能盲目套用。

总之,队列和线程池一定要根据实际来设计使用场景。
别开玩笑了,不然会有很多陷阱。