一种线程安全的handle
对象引用的正确性在多线程环境下是一个复杂的问题,请参考,处理由引用计数引起的泄漏.简单来说,我们应该尽量减少使用强引用,否则将有可能产生[处理由引用计数引起的泄漏]一文中描述的难以察觉的内存泄漏问题.也就是说,大多数情况下我们应该使用一个弱引用来指代一个对象,当我们真正需要访问这个对象的时候才将其转换成实际的对象.所以可以弱引用理解为一种handle,它只是底层对象表示的一层间接引用.
可以考虑如下场景:
我们设计了一种网络库,分为IO层和逻辑层,IO层管理实际的socket对象,而逻辑层能看到的只是socket的一个handle.逻辑层需要发送数据的时候,将handle和数据一起打包交给IO层,IO层把handle转化成实际的socket对象并完成数据发送.那么问题来了,如果在IO层收到一个发送请求时,那个handle对应的socket实际上已经销毁,那么对handle的转换就应该反映出这种情况,让转换返回一个空指针.
因为在[处理由引用计数引起的泄漏]描述的算法中,refobj *cast2refobj(ident _ident);
和atomic_32_t refobj_dec(refobj *r);
两个方法是至关重要,并且实现相对复杂,所以本文主要目的就是介绍这两个函数的作用及其正确性.
我们首先来看下refobj_dex
:
atomic_32_t refobj_dec(refobj *r)
{
atomic_32_t count;
int c;
struct timespec ts;
assert(r->refcount > 0);
if((count = ATOMIC_DECREASE(&r->refcount)) == 0){
r->identity = 0;
c = 0;
for(;;){
if(COMPARE_AND_SWAP(&r->flag,0,1))
break;
if(c < 4000){
++c;
__asm__("pause");
}else{
ts.tv_sec = 0;
ts.tv_nsec = 500000;
nanosleep(&ts, NULL);
}
}
r->destructor(r);
}
return count;
}
关键部分在引用计数为0,要准备销毁对象的分支.首先将对象的identity置0,然后在一个for循环中对尝试flag变量置1,只有当设置成功才会退出循环执行最后的析构函数.这里的主要迷惑之一是for循环和flag变量的作用是什么.让我们先看下cast2refobj
的实现在回来讨论;
refobj *cast2refobj(ident _ident)
{
refobj *ptr = NULL;
if(!_ident.ptr) return NULL;
TRY{
refobj *o = (refobj*)_ident.ptr;
do{
atomic_64_t identity = o->identity;
if(_ident.identity == identity){
if(COMPARE_AND_SWAP(&o->flag,0,1)){
identity = o->identity;
if(_ident.identity == identity){
if(refobj_inc(o) > 1)
ptr = o;
else
ATOMIC_DECREASE(&o->refcount);
}
o->flag = 0;
break;
}
}else
break;
}while(1);
}CATCH_ALL{
ptr = NULL;
}ENDTRY;
return ptr;
}
cast2refobj
的作用就是将一个handle转换成对象,如果对象未被销毁返回对象,否则返回NULL.在do循环中,首先判断handle保存的identity与实际对象的是否一致,如果不一致表明handle中存放的对象肯定已经不是原来的对象了,所以返回NULL.而当identity一致的时候,首先做的第一件事又是对flag置1.可见这个flag是这个算法的重点.现在我们来讨论flag的作用.
flag主要由两个作用:
- 防止多个线程同时进入
cast2refobj
的核心部分,让我们考虑以下场景:
有A,B,C3个线程,A线程执行refobj_dec
,在成功执行if((count = ATOMIC_DECREASE(&r->refcount)) == 0)
之后,r->identity = 0;
之前暂停.B,C则几乎同时执行cast2refobj
,这个时候因为identity还未被清0,所以B,C看到的identity必然与其持有的handle的保持一致,如果没有if(COMPARE_AND_SWAP(&o->flag,0,1))
这行代码我们看看会发生什么事情.假设B线程先执行, 它执行if(refobj_inc(o) > 1)的时候返回值应该是1,那么条件判断失败,没有将ptr设置为o,所以ptr依旧是NULL.但在执行完判断在准备执行另一个分支的ATOMIC_DECREASE(&o->refcount);
之前它也被暂停,那么当C执行if(refobj_inc(o) > 1)
它会进入ptr=o
的分支(因为refobj_inc(o)会返回2),也就是说,转换成功,而实际上返回的却是一个正准备销毁的对象.flag就是为了防止这种情况的发生,它使得多个线程执行cast2refobj
的时候,只能互斥的进入if(refobj_inc(o) > 1)
.
- 让
r->destructor
延后执行,使得执行cast2refobj
并已经进入if(COMPARE_AND_SWAP(&o->flag,0,1))
内部的线程先退出cast2refobj
,然后再执行r->destructor
.
另外还要注意的是cast2refobj
是被TRY CATCH所保护的,这样做的原因在于,在内存压力大的情况下,被销毁对象的内存可能立刻归还给系统,那么对对象的访问将会产生访问异常.我们必须捕获这个异常,同时让函数返回NULL(异常出现表明handle持有的对象必定是非法的了).
一种线程安全的handle的更多相关文章
- Java 四种线程池newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool,newSingleThreadExecutor
介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端执行一个异步任务你还只是如下new T ...
- Java四种线程池的使用
Java通过Executors提供四种线程池,分别为:newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程.newFixe ...
- Java四种线程池
Java四种线程池newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool,newSingleThreadExecutor 时间:20 ...
- 三种线程不安全现象描述(escaped state以及hidden mutable state)
hidden mutable state和escaped state是两种线程不安全问题:两者原因不同,前者主要是由于类成员变量中含有其他对象的引用,而这个引用是immutable的:后者是成员方法的 ...
- Java四种线程池newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool,newSingleThreadExecutor
1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? Java new Thread(new Runnable() { @Override public void ru ...
- Java通过Executors提供四种线程池
http://cuisuqiang.iteye.com/blog/2019372 Java通过Executors提供四种线程池,分别为:newCachedThreadPool创建一个可缓存线程池,如果 ...
- JAVA四种线程池实例
1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? Java 1 2 3 4 5 6 7 new Thread(new Runnable() { ...
- AsyncTask两种线程池
AsyncTask两种线程池 http://bbs.51cto.com/thread-1114378-1.html (API 3.0以后): 1.THREAD_POOL_EXECUTOR, ...
- Java 四种线程池的用法分析
1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? new Thread(new Runnable() { @Override public void run() { ...
随机推荐
- jquery 编码解码
中文转Unicode:HttpUtility.UrlEncodeUnicode(string str); 转换后中文格式:"%uxxxx" 举例:"柳_abc123&qu ...
- 无线安全专题01--kali破解WPA
最近一直用flask写自己的博客框架,等过段时间才能将其分享出来,所以在此期间决定开个无线安全的专题,分享一些无线安全方面的知识. 好了,废话不多说,咱们进入今天的主题,使用kali linux上的无 ...
- 部分具有统计功能的TSQL语句(例如DBCC语句,全局函数,系统存储过程)
部分具有统计功能的TSQL语句(例如DBCC语句,全局函数,系统存储过程) 这些功能也能帮助用户了解和监控SQLSERVER的运行情况 DBCC语句,DBCC语句是SQL2005的数据库控制台命令 D ...
- Orchard Oracle 支持
Orchard v1.7发布了,非常不错的一个升级,为了支持Oracle,基本上和v1.6版差不多,只是工作流相关的表有名字超长问题,改个名就可以了,作了个patch包,发布在: https://or ...
- Linux Kernel 3.11 正式版发布
Linus 发布 了 3.11 版本的 Linux 内核.该版本值得关注的新特性有: Lustre 分布式文件系统.透明的 ARM 架构的大数据页支持:ARM64 上的 Xen 和 KVM 虚拟化:O ...
- 使用 Python 进行稳定可靠的文件操作
程序需要更新文件.虽然大部分程序员知道在执行I/O的时候会发生不可预期的事情,但是我经常看到一些异常幼稚的代码.在本文中,我想要分享一些如何在Python代码中改善I/O可靠性的见解. 考虑下述Pyt ...
- linux下解决端口被占用问题
查找被占用的端口: netstat -tln netstat -tln | grep 8080 查看端口属于哪个程序 lsof -i :8080 杀掉占用端口的进程: kill -9 进程ID ...
- TypeScript开篇:尝点新鲜和甜头
返回TS学习总目录 快速开始 我们通过创建一个简单的web应用来开始使用TypeScript.获得TS工具的方法主要有两种,一种是通过NPM(Node包管理器),另一种是通过VS2012安装TS的插件 ...
- [MFC] CList
1.CList说明 类CList支持可按顺序或按值访问的非唯一对象的有序列表.CList 列表与双链接列表行为相似. template < class TYPE, class ARG_TYPE ...
- paip.提升中文分词准确度---新词识别
paip.提升中文分词准确度---新词识别 近来,中文每年大概出现800---1仟个新的词.. 60%的分词错误是由新词导致的 作者Attilax 艾龙, EMAIL:1466519819@qq. ...