单线程写多线程读安全的结构体

大型网络游戏服务器的逻辑大多采用单线程设计,典型的就是一个线程处理一个区域(地图),跨区域通过跳转实现,这样,不同区域的对象在逻辑上是不发生交互的。

这样在一台服务器上开启N个线程就可以处理N个区域。但一个线程处理一个区域毕竟有其瓶颈,如果一个区域内挤进了过多的玩家就会导致为那个区域服务的线程

不负重和,表现就是那个区域中的玩家发现操作响应变得不及时.

最近一段时间在思考如何能并行的利用多进程多机器为同一片区域服务,这样可以通过多开几个进程提升单区域的承载量。一种方式是类似bigworld全分布式设计,

同一区域中的对象可以分布在N个进程/机器中。以一个玩家为例,在一个进程中其是主动对象,所有对这个玩家对象的属性操作都必须在主动对象上执行.在其它进

程中的对象要跟这个玩家交互,例如战斗,所以这个玩家对象必须在其主动对象所在进程以外有副本对象,这样在另外一个进程中的对象可以根据这个玩家的副本

对象的属性值计算战斗伤害,抵抗等等,然后将属性变更请求发回给玩家所在的主动对象,执行实际的属性变更.以这种方式,主动对象的属性变更必须通知到所有

的副本对象。由于主动对象的属性变更后需要通知所有的副本对象,当这个区域中对象数量变得很大时,属性变更带来的通信量将会非常大.

现在换个思路,结合多线程和多进程,在一个进程中启动多个线程,主动对象位于一个线程中,属性数据在多个线程中可见,只有主动对象所在线程能修改那个对象

的属性,其它线程只能读取,这样,同一个进程中的多个线程就免除了属性同步的需要.

对于32/64位的基本属性,其读写本身就是原子的,所以可以安全的实现一个线程写,N个线程读.但对于一些结构型的属性,最典型的就是坐标,由x,y,z三个分量

构成,对其读/写都不是原子的.为了实现原子的读写坐标,最简单的做法就是在SetPos/GetPos中加锁。但再想一想,我们要读取的只是一份完整,正确的坐标数据,

 

却可以容忍其不一定是最新的数据。所以,下面实现了一个无锁的算法实现安全的对结构体的1写N读。

#include "atomic.h"
volatile int get_count;
volatile int set_count;
volatile int miss_count;struct atomic_st
{volatile int32_t version;char data[];
};struct atomic_type
{uint32_t g_version;int32_t index;volatile struct atomic_st *ptr;int32_t data_size;struct atomic_st* array[2];    
};struct atomic_type *create_atomic_type(uint32_t size);
void destroy_atomic_type(struct atomic_type **_at);#define GET_ATOMIC_ST(NAME,TYPE)\
TYPE NAME(struct atomic_type *at)\
{\TYPE ret;\while(1)\{\struct atomic_st *ptr_p = (struct atomic_st *)at->ptr;\int save_version = ptr_p->version;\if(ptr_p == at->ptr && save_version == ptr_p->version)\{\memcpy(ret.base.data,ptr_p->data,at->data_size);\__asm__ volatile("" : : : "memory");\if(ptr_p == at->ptr && save_version == ptr_p->version)\break;\ATOMIC_INCREASE(&miss_count);\}\else\ATOMIC_INCREASE(&miss_count);\}\ATOMIC_INCREASE(&get_count);\return ret;\
}#define SET_ATOMIC_ST(NAME,TYPE)\
void NAME(struct atomic_type *at,TYPE p)\
{\struct atomic_st *new_p = at->array[at->index];\at->index = (at->index + 1)%2;\memcpy(new_p->data,p.base.data,at->data_size);\__asm__ volatile("" : : : "memory");\new_p->version = ++at->g_version;\__asm__ volatile("" : : : "memory");\at->ptr = new_p;\ATOMIC_INCREASE(&set_count);\
}
#include "util/thread.h"
#include "util/SysTime.h"
#include "util/atomic.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "util/sync.h"
#include "util/atomic_st.h"struct point
{struct atomic_st base;volatile int x;volatile int y;volatile int z;
};GET_ATOMIC_ST(GetPoint,struct point);    
SET_ATOMIC_ST(SetPoint,struct point);    struct atomic_type *g_points[1000];void *SetRotine(void *arg)
{int idx = 0;int pos = 0;while(1){struct point p;++pos;p.x = p.y = p.z = pos+1;SetPoint(g_points[idx],p);idx = (idx + 1)%100;}
}void *GetRoutine(void *arg)
{int idx = 0;while(1){struct point ret = GetPoint(g_points[idx]);if(ret.x != ret.y || ret.x != ret.z || ret.y != ret.z){printf("%d,%d,%d\n",ret.x,ret.y,ret.z);assert(0);}            idx = (idx + 1)%100;}
}int main()
{struct point p;p.x = p.y = p.z = 1;int i = 0;for(; i < 1000; ++i){g_points[i] = create_atomic_type(sizeof(p));SetPoint(g_points[i],p);}thread_t t1 = CREATE_THREAD_RUN(1,SetRotine,NULL);thread_t t2 = CREATE_THREAD_RUN(1,GetRoutine,(void*)1);    thread_t t3 = CREATE_THREAD_RUN(1,GetRoutine,(void*)1);    thread_t t4 = CREATE_THREAD_RUN(1,GetRoutine,(void*)1);    uint32_t tick = GetSystemMs();while(1){uint32_t new_tick = GetSystemMs();if(new_tick - tick >= 1000){printf("get:%d,set:%d,miss:%d\n",get_count,set_count,miss_count);get_count = set_count = miss_count = 0;tick = new_tick;}sleepms(50);}
}

 

 

上面是测试代码,开启1个写线程和3个读线程,对一个atomic_type进行争抢测试,下面是测试数据的对比,先是无锁方式的,然后是加锁方式:

get:32231360,set:8129332,miss:4922677
get:30698439,set:7218725,miss:5229885
get:30248904,set:7256191,miss:5270275
get:30127294,set:7302881,miss:5312710
get:30450684,set:7325376,miss:5291387
get:30602374,set:7210568,miss:5226397
get:30542229,set:7231159,miss:5212140

get:6829928,set:1897596,miss:0
get:7005253,set:1897336,miss:0
get:7037773,set:1893310,miss:0
get:7121759,set:1907072,miss:0
get:7136176,set:1896144,miss:0
get:7118790,set:1914569,miss:0
get:7096869,set:1913391,miss:0

可以看到如果读写线程都在争抢同一个atomic_type,无锁方式比加锁方式快了4倍左右.

下面是对100个atomic_type争抢:

get:55162216,set:11529743,miss:199049
get:56026601,set:11259984,miss:205584
get:57597092,set:11386090,miss:213338
get:57144449,set:11237366,miss:208721
get:56939791,set:11119571,miss:209850
get:56983208,set:11180208,miss:209922
get:56720621,set:11169635,miss:208338

get:17567303,set:5803621,miss:0
get:17742257,set:5636563,miss:0
get:17702530,set:5621941,miss:0
get:17179876,set:5492159,miss:0
get:16825500,set:5371242,miss:0
get:17936650,set:5715384,miss:0
get:17912971,set:5743810,miss:0
get:17050807,set:5420599,miss:0

加锁方式读的效率只有无锁方式的1/3左右,写效率大概是无锁方式的1/2.

转载于:https://www.cnblogs.com/sniperHW/archive/2013/01/07/2850256.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/403179.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux 内核打印级别

printk的打印级别 #define KERN_EMERG "<0>" /* system is unusable */ #define KERN_ALERT "<1>" /* action must be taken immediately */ #define KERN_CRIT "<2>" /* critical conditions */ #define KERN_ERR "…

时间戳转日期;日期转时间戳;

一、 拓展:如何用js将日期转换成时间戳 new Date(2022-06-01).getTime() //1654041600000 //这个safari无法将时分秒转时间戳// 推荐用这个&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&…

swoole之代码热更新实现

From: http://www.th7.cn/Program/php/201405/211057.shtml 持续的思考: 本人参与的swoole项目有幸被很多朋友使用&#xff0c;我也大力向周边的一些朋友推荐&#xff0c;随着swoole的版本迭代更新&#xff0c;已经足够稳定了&#xff0c;在阿里&#xff0c;腾讯&#xff0c;yy…

题目1168:字符串的查找删除

题目1168&#xff1a;字符串的查找删除 时间限制&#xff1a;1 秒 内存限制&#xff1a;32 兆 特殊判题&#xff1a;否 提交&#xff1a;5092 解决&#xff1a;2097 题目描述&#xff1a;给定一个短字符串&#xff08;不含空格&#xff09;&#xff0c;再给定若干字符串&#x…

C# 文件操作详解(三)---------Directory类

前面两篇介绍了File类和FileInfo类&#xff0c;对于文件的操作基本够用&#xff0c;但是后面还会补充一下FileStream相关的操作&#xff0c;例如StreamReader和StreamWriter的内容。本文主要介绍Directory类的使用&#xff0c;让我们一起看一下Directory类为我们提供了哪些操作…

js实现复制并能保留原格式粘贴;vue中点击复制粘贴功能;vue复制功能(不需要插件);

使用场景&#xff1a;vue项目中遇到点击按钮&#xff0c;复制文本的功能。复制成功后可以黏贴。 <div click"copyDesc">复制</div>methods:{copyDesc () {let url 这是复制的内容&#xff0c;可以是data的变量值或传递的参数等换行文字1换行文字2 let…

Libusb开发教程一 安装

1. 从官网下载需要使用的离线资源包&#xff1a; 进入 libusb.info 的 Download 页 下载 libusb-1.0.9.tar.bz2 下载 libusb-compat-0.1.4.tar.bz2 2. 实验平台 OS: Ubuntu16.04 Kernel: 4.4.0 3. 安装过程 <1> 拷贝步骤一中的离线包到 Linux 系统目录&#xff0c;一般…

在网上接外包

在网上接外包。这个需要一些沟通能力和英文能力 以下是一些可以接到外包的网站&#xff1a; 国内&#xff1a; http://www.taskcity.com/很难接到外包&#xff0c;今年在该网站只完成两个项目。 http://www.sxsoft.com/情况同上&#xff0c;一个小项目。 国外&#xff1a; http…

ocLazyLoad angular 按需加载

ionic 框架 1.引用 index.html 加载 <script type"text/javascript" src"lib/oclazyload/ocLazyLoad.min.js"></script> 2.注入 angular.module(starter, [ionic, oc.lazyLoad]) 3.配置 .state(app.myinfo, {url: /user/myinfo,views: {menuCo…

WIN10解包分区和磁盘分区教程

From: https://thinkpad.bbs.taobao.com/detail.html?postId5390151 新机到手第一件事情就是要系统解包和磁盘分区。建议在安装和卸载软件之前&#xff0c;进行磁盘分区&#xff0c;不然有可能只能分出原空间的50%&#xff0c;想要分更多空间&#xff0c;只能还原系统了。 一、…

调用 usb_control_msg 返回错误值 -32, Broken pipe, 对 hidraw write时 返回错误值 -32, Broken pipe

-------------------Step 1--------------------- 如题&#xff0c;使用 libusb 对 hid 类设备进行控制传输的时候&#xff0c;有时会遇到此错误&#xff0c;但是实际上传输是成功的&#xff0c;用usb分析仪等工具和程序中验证&#xff0c;比如发送数据成功后&#xff0c;让设…

element自定义图标;element自定义icon;element-ui自定义tab栏图标;

一、场景&#xff1a;element-ui本身提供了图标&#xff0c;但是不全面或者开发时候需要使用自定义图标展示。此时可以用到elemenUi的自定义图标。参考链接 二、html使用&#xff0c;和正常的element的 图标 i 标签使用一样&#xff0c;使用设置的类名class即可&#xff1a; &…

Music List

http://www.xiami.com/play?ids/song/playlist/id/1208666/type/3#loaded http://www.wlyxmusic.net/

Linux内核协议栈 NAT性能优化之FAST NAT

各位看官非常对不起&#xff0c;本文是用因为写的&#xff0c;如果多有不便敬请见谅代码是在商业公司编写的&#xff0c;在商业产品中也不能开源&#xff0c;再次抱歉This presentation will highlight our efforts on optimizing theLinux TCP/IP stack for providing network…

canvas文字居中;canvas画布文字右对齐;canvas画布文字左对齐;canvas文字自动换行;canvas设置行间距;

canvas参考手册 场景&#xff1a;画布绘制文本&#xff0c;使用 context.fillText(text,x,y,maxWidth)。文本对其也就是设置xy坐标问题。 以下的画布设定宽度假设都是width&#xff1a;500 一、canvas文本左对齐&#xff1a; 就是x轴设置为0即可。 var cdocument.getElementBy…

与朱元思书

风烟俱净&#xff0c;天山共色。从流飘荡&#xff0c;任意东西。自富阳至桐庐一百许里&#xff0c;奇山异水&#xff0c;天下独绝。 水皆缥碧&#xff0c;千丈见底。游鱼细石&#xff0c;直视无碍。急湍甚箭&#xff0c;猛浪若奔。 夹岸高山&#xff0c;皆生寒树&#xff0c;负…

nginx 405 not allowed问题的解决

From: http://www.cnblogs.com/mingaixin/p/4285329.html Apache、IIS、Nginx等绝大多数web服务器&#xff0c;都不允许静态文件响应POST请求&#xff0c;否则会返回“HTTP/1.1 405 Method not allowed”错误。 例1&#xff1a;用Linux下的curl命令发送POST请求给Apache服务器上…

php判断 二维数组中 是否 存在某个一维数组

<?php $arrs array (array (286,127.0.0.1 ),array (287,127.0.0.1 ),array (288,127.0.0.1 ),array (289,127.0.0.1 ) );$row array (289,127.0.0.1 );foreach ( $arrs as $arr ) {if(!judgeEqual($arr,$row)){echo 123;}//print_r ( $arr ); }//使用数组的差集来判断…

Linux文件去掉^M

1. 直接用 vim 编辑器打开文件&#xff0c;然后把字符串 "^M" 替换成空的就可以了。命令如下&#xff1a; 输入 &#xff1a; 号进入底行模式&#xff0c;然后输入&#xff1a; %s/\r//g &#xff0c;再 Enter 就可以了。 2. 使用现成的工具。命令如下&#xff1a; …

el-cascader回显失败;el-cascader回显不出来

—我的是省市联动&#xff0c;选择时候是正常的&#xff0c;得到的绑定值是数组 [‘安徽’,‘黄山’]&#xff0c;没问题&#xff1b; 但是在详情查看时候&#xff0c;回显成[‘安徽’,‘黄山’]&#xff0c;下拉框却不显示回显值。 —原因&#xff1a;虽然data里初始化声明变量…