游戏服务器开发指南(五):避免死锁

C++并发编程中的死锁避免与锁管理策略 在多线程编程中,死锁是当多个线程因竞争资源而陷入永久等待的常见问题。其核心原理涉及互斥条件、持有并等待、非抢占条件和循环等待这四大必要条件。通过合理使用C++标准库中的RAII锁管理工具如std::scoped_lock和std::lock,可以有效避免死锁并提升并发性能。层级锁作为一种设计模式,通过强制规定锁的获取顺序来预防死锁,虽然会带来轻微性能开销,但能显著提高系统稳定性。实际工程中,结合原子操作和协程等现代C++特性,可以在保证线程安全的同时优化性能。这些技术在支付系统、游戏服务器等高并发场景中具有 阅读详情

大家好!我是长三月,一位在游戏行业工作多年的老程序员,专注于分享服务器开发相关的文章。

这次的文章由于笔者五一节前工作任务堆积,直到放假才有时间写完。本次的主题是避免死锁,是并发类别下的第三篇。

死锁的原因是多线程对资源的竞争形成了循环等待的情形,导致彼此都阻塞而无法执行下去。形成死锁后,不仅线程无法再被使用,而且线程持有的资源(锁)也无法再被其他线程获取,这会导致程序的全部或部分功能无法正常运行,对于游戏服务器来说需要重启才能恢复。避免死锁也要从避免出现资源的循环等待着手。

第一种方法是保证嵌套锁的加锁顺序。当有多个锁嵌套时,如果能保证加锁顺序是固定的,如永远是A -> B -> C,那么就不会发生死锁。例如,想象一个两人组队的游戏场景,A玩家向B玩家发送组队邀请,同时B玩家也向A玩家发送组队邀请,为了避免两个玩家之间同时创建两份组队邀请,我们为组队邀请的操作加上A、B两个玩家的锁,只允许第一个邀请发送成功,对应的Java代码如下:

public void invitate(Player fromPlayer, Player toPlayer) {
	synchronized (fromPlayer) {
		synchronized (toPlayer) {
			createInvitatiion(fromPlayer, toPlayer);
		}
	}
}

invitate(playerA, playerB);	// A玩家发送邀请给B

invitate(playerB, playerA);	// B玩家发送邀请给A

这样的代码可能导致死锁,死锁发生在A、B玩家的线程分别持有自己玩家的锁并请求对方玩家的锁。解决办法就是对这两个玩家按id加以排序,修改后的代码如下:

public void invitate(Player fromPlayer, Player toPlayer) {
	Player smallIdPlayer = fromPlayer.getId() < toPlayer.getId() ? fromPlayer.getId() : toPlayer.getId();
	Player bigIdPlayer = fromPlayer.getId() < toPlayer.getId() ? toPlayer.getId() : fromPlayer.getId();
	synchronized (smallIdPlayer) {	// id较小的玩家锁先获取
		synchronized (bigIdPlayer) {	// id较大的玩家锁后获取
			createInvitatiion(fromPlayer, toPlayer);
		}
	}
}

invitate(playerA, playerB);	// A玩家发送邀请给B

invitate(playerB, playerA);	// B玩家发送邀请给A

在某些场合锁嵌套不是必需的,这时可以使用开放调用(open call)。开放调用意思是不在锁中调用外部方法,而是改为在锁外调用,这样能在外部方法加锁情况未知的情况下,避免出现锁嵌套。使用开放调用还可以缩小加锁范围,提升程序的可伸缩性。例如,在上一篇文章《降低同步开销》中讲过的代码优化:

int joinRoom(int playerId) {
	synchronized(this) {	// 房间锁从方法级别移到这里
		int errorCode = canJoin(playerId);	// 先判断能否进入
		if (errorCode != 0) {
			return errorCode;	// 不能进入则返回错误码
		}
		players.add(playerId);	// 加入房间中玩家集合
		if (players.size() >= 5) {	// 当玩家数量满5人时开启战斗
			startFight();
		}
	}	
	notifyAll(playerId);	// 将进入消息推送给房间中其他玩家
	db.decreaseLeftFightNum(playerId);	// 今日剩余可战斗次数减1,计入db
}

将db.decreaseLeftFightNum(playerId)移出同步块就是改为开放调用。

另一种避免死锁的方法是使用定时锁和轮询锁。定时锁可以设置一个超时时间,当超过这个时间还未获得锁时不会继续阻塞等待,而是返回失败状态,程序员可以根据返回状态的不同,决定是继续执行同步块内部的代码,还是回滚当前操作。一种特殊的情况是是不带超时时间的定时锁,在这种情况下,尝试获得锁会立即返回成功或失败,失败后可以间隔一小段时间重试获取锁,直至超时操作失败,这种情况下的锁称为轮询锁。由于定时锁和轮询锁都避免了无限阻塞等待锁,所以它们不会造成死锁。以下代码就是使用轮询锁修改上面的invitate方法,避免死锁:

	public boolean invitate(Player fromPlayer, Player toPlayer) {
		long timeout = 2000L;
		long stopTime = System.nanoTime() + timeout;
		while(true){
			if(fromPlayer.lock.tryLock()){
				try{
					if(toPlayer.lock.tryLock()){
						try{
							return createInvitation(fromPlayer, toPlayer);
						} finally {
							toPlayer.lock.unlock();
						}
					}
				} finally {
						fromPlayer.lock.unlock();
					}
				}
				if(System.nanoTime()>= stopTime)// 若超过超时时间,则操作失败
					return false;
				sleepRandomInterval();	// 休眠一小段随机的时间
			}
	}

如果不用加锁的方式处理线程同步,而是改成异步或者CAS,那么都不会产生死锁。 异步不会造成线程阻塞,不过会引入处理回调额外的代码复杂度(callback hell)。CAS是硬件支持的非阻塞原语,行为模式上有点类似于前面提到的轮询锁,也是需要不断轮询才能让代码进行下去,不过CAS相对加锁来说也会显著提升代码复杂度。

总之,对于游戏服务器来说,程序死锁通常会造成功能失效的严重问题。如果一定要用锁,那么最优的解决办法是保证嵌套锁的顺序永远一致,另外避免非必要的锁嵌套。另一种思路是彻底摒弃锁,改用异步的编程模式(例如Skynet、Actor),这样就不用考虑死锁,不过会带来额外的代码复杂度。

游戏服务器开发指南(三):设计高效的线程模型 这样设计的问题是,由于战斗计算是CPU密集型的计算任务,当房间数量增多到一定程度时会达到单核性能瓶颈,无法在原定的一帧时间内执行完所有的房间计算,导致掉帧的情况出现。同样典型的场景还有MMO中的大地图,由于整个地图运算量巨大,通常会考虑使用多线程分担计算量,拆分角度通常是按地图区域,每个区域分配一个线程,或者是按业务类型,每种独立的业务逻辑分配一个线程。例如,恰好玩家分配到线程1上的数量较多,而且来自这部分玩家的请求数也较多,就会造成线程1繁忙,而其他线程空闲的情况。一周一次的系列分享又与大家见面了。 阅读详情

相关推荐

Erlang构建的小型游戏服务器实战指南

Mnesia是一种为Erlang环境量身定做的高性能分布式数据库系统。它内置于Erlang/OTP中,提供了一个独特的组合,以满足实时系统的需要。Mnesia具有以下显著特点:内存和磁盘数据库:Mnesia能够在内存和磁盘上存储数据,提供快速的读取和持久性。事务性操作:支持ACID事务,保证了操作的原子性、一致性、隔离性和持久性。动态分布式数据库:Mnesia表可以分布在网络中的多个节点上,实现数据的自动复制和负载均衡。可编程性。

weixin_42181686的博客 1117

3分钟定位!Skynet工作线程锁死问题深度剖析与解决方案

在游戏服务器开发中,工作线程锁死是影响Skynet框架稳定性的常见隐患。本文将从问题诊断、源码分析到解决方案,为你提供一套快速定位并解决线程锁死的完整指南,帮助开发者在3分钟内精准定位问题根源。 ## 一、Skynet线程模型与锁死风险 Skynet作为轻量级游戏框架,采用多线程模型处理并发任务。其核心线程架构通过`skynet_server.c`实现,主要包含: - **工作线程**:执行服

gitblog_01077的博客 518

Java后端实战:钉钉互动卡片消息从配置到生产级对接

在企业级消息推送场景中,机器人通知已从简单的文本向富交互演进。其核心原理在于通过可交互的卡片模板,将传统单向通知升级为双向、有状态的微型应用界面,实现了用户无需跳转即可完成操作的技术价值。这广泛应用于审批、任务确认、数据填报等需要即时反馈的业务流程。本文聚焦于钉钉平台,深入剖析其互动卡片消息机制,并基于Java后端,系统讲解如何实现包括安全验签、异步回调与卡片动态更新在内的生产级对接方案,涵盖配置管理、服务降级等工程实践关键点,助力构建稳定高效的企业消息交互服务。

weixin_30617737的博客 435

skynet中的各种锁

  最近读skynet c语言部分的源码,发现有好多锁的使用和gcc提供的一些原子操作。看到这些东西,对于我这个newbee来说实在有些hold不住。但为了了解并进一步掌握,还是决定好好分析一下。不足之处望指正。   自旋锁(spinlock) 和 互斥锁(mutex) 对比   自旋锁:得到锁之前是在一个循环中空转,直到得到锁为止,那么就有三种可能 1:很短时间就得到锁,由于是空转,没有sl...

weixin_33725807的博客 237

CH341的I2C流模式(StreamI2C)到底怎么用?从EEPROM读写到自定义设备,一篇讲清参数配置逻辑

本文详细解析了CH341的I2C流模式(StreamI2C)的使用方法,从EEPROM读写到自定义设备的参数配置逻辑。通过深入分析I2C协议基础、StreamI2C参数映射关系及典型设备配置示例,帮助开发者快速掌握StreamI2C的应用技巧,解决实际开发中的常见问题。

weixin_30932215的博客 448

Halcon棋盘格畸变矫正

通常我们使用的相机都是带有一定的畸变的,只是畸变的大小不同。对于我们常见的测量项目来说,对测量精度的要求很高,通常从相机镜头上进行优化,但是即使镜头优化或者成本原因,造成图像仍有一定的畸变时,我们就可是使用棋盘格标定板,进行畸变矫正。

m0_51559565的博客 3396

skynet watchdog,gate源码分析

前一篇skynet watchdog和gate讲解了watchdog和gate的流程图,这篇就代码剖析一下。 watchdog和gate旨在简化网络模块的设计,在实际的项目中我们通常会都会用到这种模式 ,提高生产效率。前面讲的集群模块中也用到了这种模式。 skynet中gate.lua文件实际上引用了gateserver.lua,他们共同构成了一个服务,消息的主体回调处理在gateserver...

专注于网络编程,游戏后台,高并发 3364

从BUCK-BOOST到反激:一个‘拆电感’的视角,轻松理解多路输出交叉调整

本文从BUCK-BOOST电路到反激变换器的演变入手,通过‘拆解电感’的独特视角解析多路输出交叉调整机制。深入探讨了反激电路的能量暂存方式、传递路径变化及电压关系,并结合具体参数对比揭示其特性。针对多路输出中的电压平衡问题,提供了绕组排布优化、元器件选型等实战技巧,帮助工程师有效提升电源系统的稳定性与效率。

weixin_33738578的博客 437

go hive skynet_云风的 BLOG

其实,对于我们的网游服务器来说,分布式运算,以及分布式运算的稳定性并不是第一位的。我们更需要的是充分挖掘多核处理器的单机性能以达到高实时的相应速度。分布式运算和并行运算显然不是同一类问题。skynet 只要专注于并行处理就可以了,顺带支持一下分布式可以说是为以前的设计做的兼容。事实上,即使在过去基于 erlang 的版本,我们依然编写了两个独立的系统间通讯的模块,而不全部依赖于 erlang 自己...

weixin_39969568的博客 482

用Python玩转ROS机械臂:YOLO目标检测+MoveIt!自动抓取仿真教程

本文详细介绍了如何利用Python集成YOLO目标检测与ROS的MoveIt!运动规划库,实现机械臂的智能抓取仿真。通过Gazebo仿真环境,从视觉感知、坐标转换到运动规划与执行,构建了一套完整的自动化抓取系统,为机器人视觉抓取应用提供了可复现的实践方案。

ppp33的博客 552

DB文件在编程中的作用及示例代码

通过使用DB文件,我们可以实现数据的持久化存储,并能够高效地进行数据的增删改查操作。在本文中,我们以SQLite数据库为例,演示了如何使用Python语言创建和操作DB文件的示例代码。在实际的编程中,我们可以根据具体的需求和使用的数据库系统来进行更复杂的操作和优化。通过使用DB文件,我们可以实现数据的持久化存储,确保数据在程序关闭后也能被保留,并且能够高效地进行数据检索和更新。在本文中,我们将探讨DB文件的作用,并提供一些示例代码展示如何在编程中使用DB文件。上述代码中,我们使用SQLite提供的。

CodeSpark的博客 1821

pthread_cancel引起的死锁

最近发现程序无法正常终止,

huanzai2的专栏 1544

STM32入门教程:智能马桶盖控制

通过编写相应的代码,您可以根据需要实现其他功能,例如座圈温度调节和液晶显示屏显示等。初始化配置 首先,使用STM32CubeMX创建一个新的工程,并选择您所使用的STM32单片机型号。智能马桶盖是一种集成了多种功能的智能家居设备,通过控制芯片来实现自动冲水、座圈加热、座圈温度调节等功能。在本教程中,我们将使用STM32系列单片机来控制智能马桶盖的各个功能。以上代码演示了如何通过STM32的GPIO控制引脚来控制智能马桶盖的冲水和座圈加热功能。写代码 接下来,我们将通过编写代码来控制智能马桶盖的各个功能。

2401_85258012的博客 657

C++之,我是如何解决多线程死锁与资源竞争问题的

在C++编程的实际工作场景中,多线程开发能够显著提升程序的运行效率,但也引入了一系列棘手的问题,其中死锁和资源竞争问题尤为突出。我在参与一个大型游戏服务器开发项目时,就深刻体会到了这些问题带来的困扰。下面,我将详细讲述我遇到的问题、解决问题的思路和过程,以及相关的代码示例。

wj_rdk的博客 1622

YOLO26 多模态融合技术:RGB+红外/深度/雷达融合

YOLO(You Only Look Once)是一种流行的目标检测算法,其最大特点是快速且端到端地完成图像中所有目标的检测与分类任务。YOLO首次在2016年由Joseph Redmon等人提出,彻底改变了传统目标检测(如R-CNN系列)的思路,被广泛应用于自动驾驶、视频监控、工业质检等场景。

一键难忘的博客 3523

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

游戏服务器开发指南(目录)

本目录用于游戏服务器开发指南系列,分门别类存放文章的链接地址,方便读者快速索引。目录按不同的主题组织,如网络通信、数据存储等,每个主题会包含若干篇文章。个人计划是平均每周写一篇,写作基于平时工作中产生的灵感,因此不一定会按照主题的排列顺序来。

长三月的游戏开发 400

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

数据库 - 死锁深度解析:定义、检测与预防

死锁深度解析:定义、检测与预防引言一、死锁的定义二、死锁发生机制三、死锁检测方法四、死锁预防技术五、死锁避免技术六、死锁恢复技术七、实际案例分析八、总结在计算机科学和软件工程中,死锁是一个重要且复杂的问题,尤其在并发程序设计中。它发生在多个进程或线程相互等待,形成一种环形依赖,从而导致它们都无法继续执行。本文将深入探讨死锁的定义、发生机制、检测方法、预防技术以及实际案例。死锁是指在多线程或多进程环境中,两个或多个进程或线程由于相互持有对方所需的资源,导致它们都无法继续执行。互斥条件。

逍遥Sean的博客 1285

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

游戏服务器开发工程师成长全攻略

游戏服务器开发工程师负责游戏服务器端核心功能模块的设计开发与维护,要求精通C++/Java/Go等语言,熟悉TCP/UDP、MySQL、Redis等技术,具备高并发处理和架构设计能力。常见面试题涵盖网络协议、同步机制、分布式架构、数据安全等方面。学习路线包括计算机网络、编程语言、游戏服务器核心技术及项目实战。职业成长路径从初级开发逐步晋升至技术专家或管理岗。该岗位需要扎实的技术功底、良好的协作能力和持续学习意识。

本博客聚焦游戏开发技巧、创业实战心得及大学热门课程干货。这里既有技术深度,也有思维广度,无论你是开发者、创业者还是高校学子,都能在这里找到适合自己的成长之路! 1189

标题:打造高效稳定的游戏服务器:深度解析GeekServer

--- ## 项目介绍 欢迎走进GeekServer的世界——这是一个专为游戏设计的开源服务器框架,利用C# .Netcore的强大功能,实现了跨平台兼容性与高性能。它的核心理念是“大道至简,以简化繁”,致力于让游戏服务器的开发变得更加简单高效。 ## 项目技术分析 GeekServer采用了先进的技术栈,其中包括: - **跨平台支持**:基于.Netcore,保证了在Windows和

gitblog_00045的博客 774

【亲测免费】 死锁检测工具 DeadLock 指南

死锁检测工具 DeadLock 指南 项目介绍 DeadLock 是一个由 CodeDead 开发并维护的开源项目,专注于解决和预防在多线程或并发环境中的死锁问题。该项目提供了一套高效且灵活的工具,帮助开发者识别和解决应用程序中潜在的死锁状况。通过深入分析线程间的资源获取关系,DeadLock 能够在运行时检测出可能导致程序挂起的循环依赖关系,从而保障系统的稳定性和响应性。 特性亮点: 实时死...

gitblog_00532的博客 1108

程序员必看:如何用银行家算法避免死锁?附Python实现代码

本文深入解析了银行家算法如何通过动态检查资源分配请求,确保系统始终处于安全状态,从而有效避免死锁。文章从死锁的必要条件讲起,详细阐述了算法的核心概念、安全序列的寻找过程,并提供了清晰的Python实现代码,帮助开发者将这一经典理论应用于数据库连接池、任务调度等实际并发资源管理场景。

b3n4m5q6w7的博客 526

C++实现银行家算法:原理详解与死锁避免实战

在并发编程与操作系统资源管理中,死锁是多个进程因循环等待资源而无法推进的系统性问题。其核心原理涉及互斥、持有并等待、不可抢占和循环等待四个必要条件。为避免死锁,银行家算法作为一种经典的死锁避免策略,通过预判资源分配后的系统安全性来保障系统稳定运行。该算法的技术价值在于其严谨的理论基础,能在死锁发生前进行前瞻性检查,对于构建高可靠性的服务器、嵌入式系统等至关重要。其应用场景广泛,从传统的多线程资源调度到现代的容器编排平台资源配额管理,其安全状态评估思想一脉相承。本文通过C++源码实战,深入解析了银行家算法的数

weixin_31220401的博客 298

揭秘GeekServer核心:Actor模型如何解决游戏服务器并发难题?完整技术解析

在当今游戏服务器开发中,**并发处理**是开发者面临的最大挑战之一。当数千甚至数万玩家同时在线时,如何保证数据一致性、避免竞态条件、提高服务器性能?GeekServer基于Actor模型的创新设计为这些问题提供了终极解决方案!🚀 ## 🌟 什么是Actor模型? Actor模型是一种并发计算模型,它将每个独立的计算单元视为一个"演员"(Actor)。每个Actor拥有自己的状态,并且只能通

gitblog_01156的博客 378

C++ 互斥量、死锁

目录 案例: 共享数据: 1、互斥量引入 方法一:lock()、unlock() 方法二:std::lock_guard() 2、死锁 解决方案: 案例: 游戏服务器,收到玩家的指令,然后处理指令,使用一个list容器来收集指令,用两个线程来维护接收、处理指令的操作。 共享数据: 如果对于共享数据是只读操作的话,不需要考虑互斥量问题 如果对共享数据需要进行读写操作的话...

猫ER 682

GeekServer:革命性.Netcore游戏服务器框架,让Unity开发效率提升300%的终极指南

GeekServer是一个基于.Netcore开发的高性能游戏服务器框架,专为Unity游戏开发者打造。它提供了分区分服架构、不停服热更新机制和跨平台部署能力,让游戏服务端开发变得简单高效。无论是独立开发者还是大型团队,都能通过GeekServer快速构建稳定可靠的游戏后端系统。 ## 为什么选择GeekServer?三大核心优势解析 🚀 ### 1. 卓越性能:轻松应对高并发挑战 Gee

gitblog_00362的博客 423

如何使用NoahGameFrame Actor模型构建高并发游戏服务器:完整实战指南

NoahGameFrame是一个快速、可扩展的分布式游戏服务器引擎,特别适合构建实时多人在线游戏(如MMORPG/MOBA)。其核心优势在于内置的Actor模型实现,能够有效解决高并发场景下的资源竞争和线程管理问题,让开发者专注于游戏逻辑而非底层架构。 ## 🚀 Actor模型:游戏服务器的并发利器 在传统游戏服务器开发中,多线程并发处理往往面临数据竞争、死锁等挑战。Actor模型通过将游戏

gitblog_00670的博客 802
上一篇: 游戏服务器开发指南(四):降低同步的开销
下一篇: 用Java实现Actor模型(模仿Skynet)
长三月
博客等级 码龄9年 228粉丝 80原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值