作者:Yahle
曾用网名:Dogvane
原载:http://www.cnblogs.com/yahle
版权所有。转载时必须以链接形式注明作者和原始出处。
1 多线程下数据库并发更新的处理
1.1 背景
不知道大家在玩《Travian》时有没有做过这样的事情:
同时打开多个集结点,并设定好要出发的士兵及数量,在快到压秒的时候,快速切换页面,不断的点确定,以确保游戏不会通讯问题导致压秒失败。
再看一个教科书里经常提到的数据库脏数据的案例:
A操作从表里获得数据D=10,在计算的时候,线程刚好进行切换,切换到B,B也需要操作D,并从数据库里取道值为10,在进行简单操作(D=D- 2)后将D=8的值写回数据库。B操作处理结束后,线程再切换回A操作,这时A在做自己的操作时,仍然采用先前取到D(10)的值,在进行一个简单操作(D=D-1)后,仍然写回数据库。这时,数据库里的值变为9,而实际上D的值应该是7(D=10-2-1).。造成这个问题主要是因为CPU在执行A、B操作时没有按照顺序来执行,而是让B抢先在A执行完之前执行,导致它们在计算D的时候,因为数据没有同步而发生写入脏数(A的数据覆盖了B的数据)据的问题。
A操作伪代码:
{
D=GetDB()
D=D-1
SetDB(D)
}
B操作伪代码:
{
D=GetDB()
D=D-2
SetDB(D)
}
一个简单的图表表示它们的操作:
CPU
|
A操作
|
B操作
|
数据库当前值
|
|
D=GetDB()
|
|
10
|
线程切换
|
|
D=GetDB()
|
10
|
|
|
D=D-2
|
10
|
|
|
SetDB(D)
|
8
|
线程切换
|
D=D-1
|
|
8
|
|
SetDB(D)
|
|
9
|
仿照第一个例子,当一位玩家同时打开多个页面点出兵进攻后,这些这些请求会同时到达服务器,服务器会根据这些Http请求创建相应的线程来处理进攻动作:
进攻的伪代码:
{
村庄士兵数量=GetDB()
if (村庄士兵数量 > 这次进攻士兵数量)
{
村庄士兵数量=村庄士兵数量-这次进攻士兵数量
SetDB(村庄士兵数量)
}
}
按照第二个脏数据的例子,应该很容易想到,我们在这次进攻的时候,很有可能派出了2只部队,但是只减少了1只部队的士兵。
多线程未同步是造成游戏bug的原因之一。
1.2 多线程并发与互斥
关于什么是多线程,以及多线程下面的同步及互斥的方法我这里就不过多的介绍了,相关内容可以到网上搜索,本文主要是讨论同步的时机以及避免死锁
cnblogs.com相关主题
1.3 WebGame里多线程数据同步的方法
1.3.1 在asp.net下用lock进行加锁操作
在我们的WebGame里,采用asp.net的lock方法。具体就是在方法体里,用lock里锁住一个对象,使其它方法在访问这个对象的时候被阻塞。当第一个访问对象的线程退出并释放锁以后,其它的线程才能取消阻塞状态继续操作该对象。
我们通过修改上面进攻的伪代码,增加lock操作:
{
lock(锁定的对象)
{
村庄士兵数量=GetDB()
if (村庄士兵数量 > 这次进攻士兵数量)
{
村庄士兵数量=村庄士兵数量-这次进攻士兵数量
SetDB(村庄士兵数量)
}
}//unlock(锁对象)
}
CPU执行流程表:
CPU
|
线程A
|
线程B
|
|
|
lock(锁对象)
|
|
|
|
村庄士兵数量=GetDB()
|
|
|
|
if (村庄士兵数量 > 这次进攻士兵数量)
|
|
|
线程切换
|
|
lock(锁对象)
|
对象被锁,线程进入阻塞状态
|
线程切换
|
村庄士兵数量=村庄士兵数量-这次进攻士兵数量
|
|
|
|
SetDB(村庄士兵数量)
|
|
|
|
//unlock(锁对象)
|
|
|
线程切换
|
|
村庄士兵数量=GetDB()
|
A线程释放锁,B线程被唤醒
|
|
|
if (村庄士兵数量 > 这次进攻士兵数量)
|
|
|
|
|
|
|
|
村庄士兵数量=村庄士兵数量-这次进攻士兵数量
|
|
|
|
SetDB(村庄士兵数量)
|
|
1.3.2 锁的粒度
上面的伪代码是对游戏里的逻辑代码进行了加锁处理,但只是简单的描述该方法体里需要进行加锁以及加锁的范围。但在实际的代码里,我们需要明确lock里锁定的对象。如果这个对象选择不合适,很有可能会造成性能损失或者死锁。
1.3.2.1 数据库锁
数据库锁是最一种简单的方法就,凡是在存在发生数据库写操作的代码里,都需要进行加锁处理,并同意用一个数据库锁对象。
这种方法使用简单,不容易造成死锁。当存在的问题也是明显,就是在发生写数据库操作时不能并发操作,这点特别是当用户访问量增大可能会造成一定的性能瓶颈。
1.3.2.2 村庄锁
为了提升写数据库的效率,我们必须解决锁粒度过大的问题,因此在我们的游戏系统里,对锁的粒度进行的细化,细化到村庄级别的对象。
在游戏一张村庄表对应是整个游戏里所有的村庄对象,而一个村庄对象在村庄表里只是一条记录。在使用数据库锁时,其实是告诉其它方法,现在我要写数据库,大家都等一下,等我写好后再写。当我们将锁的对象细化到村庄(一条数据库表记录)的时候,实际是告诉数据库,我现在要修改XXX村庄,大家都别动它,但你要修改YYY村庄我不管。
死锁
在前面虽然降低了锁的粒度,提高了数据库并发性能,但随之而来就很容易发生一个问题--死锁。
例如当两个村庄需要发生交易,这时我们需要同时修改A、B两个村庄对象,需要对其进行顺序锁定(先锁定A,再锁定B),这时候,又发生另外一个操作,也需要同时对A、B两个村庄进行锁定,恰巧这个锁定的顺序是B、A。这样就造成两个现在互相等待形成死锁。
中间变量解决死锁
对于死锁,在关于多线程介绍方面有很多解决方案,这里就不过多阐述。在WebGame里预防死锁,可以采用结合游戏的操作流程,对游戏处理流程及数据进行拆分,来预防死锁问题。简单的来说,就是将涉及2个村庄修改的流程拆分为2个流程,并用中间变量予以表示,两个处理流程涉及变化的值都在中间变量里予以保存。
以前面提到的交易为例,在游戏设计里,两个村庄在交易的时候,并不是瞬时交易,而是通过商人进行运输并交易。这样我们就以商人作为中间变量。
A<-->C<-->B
在上图里,交易开始,是由玩家触发交易事件,这时以村庄A为锁对象,进行锁定。C作为要交易资源从村庄A里被扣除。并将A修改后的数据回写到数据库。等过了一段事件后,商人C到达了目的地,这时由系统触发后续的交易事件,这时以村庄B作为锁对象进行锁定。村庄B在获得资源后写回数据库,整个交易事件就算完成了。
当然实际游戏的交易比上面的例子稍微复杂一点点,因为交易双方都有资源的减少与获得。完整的流程应该是需要锁定4次,并产生2个中间变量。
村庄A-->商人1-->村庄B
村庄A<--商人2<--村庄B
游戏里其它地方的锁定
基本上,凡是某个事件涉及到两个村庄修改的地方,都可以用上面的锁定的方法对处理流程进行修改。例如村庄A攻打村庄A。当然其它事件在数据修改方面只涉及1个村庄,那么就不需要怎么麻烦,直接对村庄加锁锁定即可。
好在在策略类的WebGame里涉及两个村庄的情况不多,涉及到的基本可以用中间变量对操作流程进行拆分,因此这种锁定方式在策略类WebGame还是比较合适的。当然实在不行也只能按照以往的预防死锁的方法进行处理
1.4 非Asp.net里同步的方案
1.4.1 Java
java在线程同步机制上与asp.net基本一致,因此上面所述的asp.net的方法也适合与java
1.4.2 php
了解不多,不过好像php没有线程锁与同步这个概念,如果直接通过语言环境进行同步可能比较困难。
不过在MySQL里,存在一个表锁定的方法,可以通过lock table的方法锁定表,不允许其它MySQL用户去进行操作。基本上和前面提到的数据库锁类一样,只不多执行方法的时候是在MySQL端执行。
分享到:
相关推荐
测试部分可能会比较不同并发策略下的性能差异,如单线程、多线程无锁、多线程带锁等。 总结来说,理解和掌握这些知识点对于在C#中高效且安全地实现多线程读写SQLite数据库至关重要。通过合理利用多线程并正确实施...
在IT领域,多线程定时器是一个非常实用的工具,特别是在开发Web应用程序和Windows桌面应用(Winform)时。这个工具允许程序员在多个线程环境中设置定时任务,从而实现后台处理、定期检查更新或者执行一系列间隔操作...
在IT领域,多线程和高并发是两个关键的概念,特别是在服务器端开发、分布式系统以及高性能计算中。本文将深入探讨这两个主题,并结合实战经验,为开发者提供详细的理论知识和实践指导。 一、多线程基础 1. 线程定义...
在IT领域,多线程和高并发是两个关键的概念,特别是在服务器端开发、分布式系统以及高性能计算中。本文将基于《多线程与高并发实战手册》中的知识点,深入探讨这两个主题。 首先,多线程是指在一个程序中同时运行多...
总的来说,这个资源为开发者提供了一个基础的FLASK应用模板,其中包括了处理并发请求的多线程策略,以及数据库管理和配置文件读取的解决方案。开发者可以根据自己的需求对这个模板进行修改,例如添加更多的功能,...
在Web服务器中,多线程尤其重要,因为服务器需要处理来自不同客户端的并发请求。基于多线程的Web服务器设计,可以确保即使在高负载情况下也能高效地服务。 【知识点】 1. **Java多线程基础**:Java提供了两种创建...
实践中,多线程常用于服务器的并发处理,比如Web服务器通过多线程服务来自全球的用户请求;在数据库系统中,多线程用于并发读写操作,提升数据库性能;在大数据处理框架如Hadoop和Spark中,任务被分解成多个线程并行...
2. 数据库操作:数据库连接通常昂贵且有限,多线程可以有效利用连接池,提高查询和更新速度。 3. Web服务器:多线程可以同时处理多个客户端请求,提高服务器响应能力。 通过学习本教程,你将掌握多线程的基本概念,...
1. Web服务器:处理来自多个客户端的请求,通过多线程并发响应提高效率。 2. 数据库访问:数据库操作通常涉及I/O,多线程能有效提升查询和写入速度。 3. 大数据处理:分布式计算系统中,多线程用于分片并行处理海量...
多线程计算是一种在计算机程序中同时执行多个线程(执行序列)的技术,它能够充分利用现代多核处理器的计算能力,提高程序的并发性和效率。在处理大量数据、实时响应或者进行复杂运算时,多线程是不可或缺的工具。...
在Java开发领域,构建高性能、高并发的Web应用是一项核心任务。这涉及到多个技术层面的综合运用,包括但不限于系统架构设计、线程管理、数据访问优化、缓存策略、负载均衡以及性能监控等。以下是一些关键的知识点,...
从Servlet 3.0开始,Servlet API提供了异步处理能力,允许Servlet在不阻塞线程的情况下处理请求,从而提高了系统的并发能力。不过,Tomcat 5.5.17并不支持这一特性,需要升级到更高级别的版本才能利用。 9. **请求...
5. **测试与调试**:多线程程序的调试难度较大,需要使用专门的工具和策略进行测试,确保在各种并发场景下都能正常运行。 总之,ASP.NET多线程编程是一项复杂而强大的技术,它能帮助开发者构建高性能的Web应用,但...
2. Web服务器:如Tomcat、Jetty等,使用多线程处理HTTP请求,提升并发能力。 3. 文件处理:多线程读写大文件,加快处理速度。 4. 定时任务:如Quartz,利用线程池调度定时任务,避免资源浪费。 总之,多线程是现代...
在Java中,多线程是并发编程的重要工具,允许程序同时执行多个任务,这对于处理来自不同客户端的Web请求至关重要。 在设计一个多线程Web服务器时,我们需要考虑以下几个核心知识点: 1. **线程池**:为了有效管理...
在实际开发中,多线程的运用十分广泛,例如在Web服务器处理并发请求、数据库连接池管理、后台定时任务等场景。理解并熟练运用多线程技术,能极大地提升软件的性能和用户体验。 通过上述的简单多线程实例,你可以...
多线程是现代计算机编程中的一个重要概念,尤其在处理高性能计算、并发操作以及实时系统时,多线程技术显得尤为重要。它允许程序同时执行多个任务,提高系统的效率和响应速度。 一、多线程的基本概念 1. 线程:线程...
在计算机科学中,多线程和并发是两个关键概念,特别是在高性能和响应迅速的应用程序开发中。多线程是指在一个程序中同时运行多个线程,而并发则涉及到多个任务或线程在同一时间段内交替执行。这两者都是为了提高系统...
2. **性能提升**:通过优化内存管理和线程模型,Tomcat 5在处理高并发请求时表现更加出色。 3. **安全性增强**:引入了新的安全机制,如支持SSL/TLS加密通信、增强了用户身份验证和授权等功能。 4. **可扩展性加强**...
3. 数据库操作:在并发读写数据库时,多线程可以提高吞吐量,但需注意事务的隔离性和一致性。 总之,多线程是现代软件开发中不可或缺的技术,它能提高程序的性能和用户体验。理解并掌握多线程的概念、使用方法以及...