http://www.cnblogs.com/humble/p/6238999.html
为了方便,请移驾这里看问题,分不够再加.
I want to design a function (or feature) which can create an unique object for each thread, and can dispose the object when its thread ends.
-----线程内的对象复用.
不一定要用ThreadLocal<T>
只要能实现此功能即可
http://stackoverflow.com/questions/5760347/memory-concerns-and-threadlocalt
多动手,多Google
你觉得你再解决问题吗?发一个链接知道里面在说啥吗?知道如何解决这个问题吗?
@[秦时明月]: 要不我把代码也给你写了,把你的工资也领了,这样算解决问题了吧?
这个需要自己调用Dispose释放掉吧?
你把ThreadLocal<T>去掉,你会发现,内存一样没有被释放掉,这和ThreadLocal<T>没有关系。
这是.Net垃圾回收机制和重写Finalize方法的原因,建议你看看垃圾回收机制和Finalize的原理。
I want to design a function (or feature) which can create an unique object for each thread, and can dispose the object when its thread ends.
能做到这个也行
@[秦时明月]: 把~MyObject()去掉就可以了,触发垃圾回收时就会释放掉资源。你的代码不能触发垃圾回收,你可以在线程结束时调用GC.Collect()方法看效果。
public class MyObject : IDisposable { private static readonly ThreadLocal<MyObject> threadLocal = new ThreadLocal<MyObject>(true); public static MyObject GetCurrentThreadMyObject(String name) { if (threadLocal.Value == null) { threadLocal.Value = new MyObject(name); } return threadLocal.Value; } String _name; public byte[] _bs; public MyObject(String name) { _name = name; Console.WriteLine("创建MyObject.name=" + name); _bs = new byte[(1024 * 1024 * 500)]; for (int i = 0; i < _bs.Length; i++) { _bs[i] = 3; } } public void Dispose() { threadLocal.Value = null; _bs = null; } }
https://msdn.microsoft.com/zh-cn/library/ee362195(v=vs.100).aspx
微软的文档写的好好的,为什么不看呢?
基本上就是微软希望在必要时可以由用户自己调用Dispose方法,或者等着整个对象被垃圾回收(最终内部也是用一个ThreadStatic变量来记录值的,所以就算你什么都不干也会随着线程一起被回收)
关键是垃圾回收的时间不可控,那么简单的测试代码连一次gc都触发不了自然看不到效果了。
要是微软这么智障那么ASP.NET根本跑不起来好不好
20:50 修改:
看了下java和.net的实现,两家最终分别通过Thread.threadLocals/ThreadStaticAttribute来绑定对象到特定线程上的,所以可以安心的认为在线程被回收后的某个时刻对象将被销毁(需要考虑gc时间的不确定性,如果自己写测试代码还得考虑到gc本身是异步的,需要显示等待gc完成,下面第一个回复我的应该主要就是忘记等待gc回收完成了)
不过在这个源码都公开的年代,如果不能确认就提问啊,写个博客误导他人的话多不好?
人家是在说如何设计这个功能:
I want to design a function (or feature) which can create an unique object for each thread, and can dispose the object when its thread ends.
线程什么时候结束没有人知道
我给你一个版本,用你的MSDN说得一样不可以,更何况博主的意思大概就是为了实现线程内对象复用.
using System; using System.Threading; namespace ThreadLocalTest { public class MyObject { String _name; byte[] _bs; public string Name { get { return _name; } set { _name = value; } } public MyObject(String name) { Name = name; Console.WriteLine("创建MyObject.name=" + name); _bs = new byte[(1024 * 1024 * 100)]; for (int i = 0; i < _bs.Length; i++) { _bs[i] = 3; } } ~MyObject() { Console.WriteLine("析构MyObject" + Name); } } public class CurrentThread { public static ThreadLocal<MyObject> ThreadLocal = new ThreadLocal<MyObject>( () => { return new MyObject(Thread.CurrentThread.ManagedThreadId.ToString()); },true ); } class Program { static void test1() { for(int i = 0; i < 7; i++) { Thread th1 = new Thread((state) => { var obj = CurrentThread.ThreadLocal.Value; Console.WriteLine("t.id=" + obj.Name); CurrentThread.ThreadLocal.Value=null; GC.Collect(); }); th1.Start(); } } public static void Main(string[] args) { Thread.Sleep(3000); test1(); Thread.Sleep(9000); CurrentThread.ThreadLocal.Dispose(); CurrentThread.ThreadLocal=null; GC.Collect(); Console.ReadKey(true); } } }
最后结果,一直还有1个对象内存没有释放
@hellozmh: 试了下你的代码,没有问题啊
就是有时最后一条等的比较久
稍微调整下就明显多了
using System; using System.Threading; namespace ThreadLocalTest { public class MyObject { private String _name; private byte[] _bs; public string Name { get { return _name; } set { _name = value; } } public MyObject(String name) { Name = name; Console.WriteLine("创建MyObject.name=" + name); _bs = new byte[(1024 * 1024 * 100)]; for (int i = 0; i < _bs.Length; i++) { _bs[i] = 3; } } ~MyObject() { Console.WriteLine("析构MyObject" + Name); } } public class CurrentThread { public static ThreadLocal<MyObject> ThreadLocal = new ThreadLocal<MyObject>( () => { return new MyObject(Thread.CurrentThread.ManagedThreadId.ToString()); }, true ); } internal class Program { private static void test1() { var count = 7; System.Threading.SemaphoreSlim locker = new SemaphoreSlim(count); for (int i = 0; i < count; i++) { Thread th1 = new Thread((state) => { locker.Release(); var obj = CurrentThread.ThreadLocal.Value; Console.WriteLine("t.id=" + obj.Name); CurrentThread.ThreadLocal.Value = null; //GC.Collect(); }); th1.Start(); } locker.Wait(); } public static void Main(string[] args) { //Thread.Sleep(3000); test1(); var total = GC.GetTotalMemory(true); //Thread.Sleep(9000); //CurrentThread.ThreadLocal.Dispose(); //CurrentThread.ThreadLocal = null; //GC.Collect(); //Console.ReadKey(true); } } }
@Nyarlathotep: 知道上面哥们一个内存不释放原因吗
@[秦时明月]: 垃圾回收是在另一个线程进行的,如果没有等待GC完成,就急匆匆把窗口关掉,自然看不到啦。可以查查GC类的msdn,里面有各种等待方法。
@Nyarlathotep: 你等半个小时试试
@[秦时明月]: 不需要啊,没仔细测量,我机子上他那个代码多等1-2s最后一笔就析构了
GC.GetTotalMemory(true)会直接执行GC并等待完成,用我调整后的代码立即就看得出效果了
其实你怀疑微软sb的话,最好的方式是去看源码,后来我补充了,两家的实现大的逻辑上是一样的。
反证法:如果微软这么sb,他们怎么通过自家的QA的?
@Nyarlathotep: 你没有理解我的意图,我是想设计一个线程内对象复用功能,线程结束时能自动销毁这个对象。treadlocal如果要你这样才能释放,我是不是要哭
@[秦时明月]: 两家都只能保证线程销毁后的某个时刻(GC把线程回收后)才顺手把线程相关对象回收掉。
调用GC只是让测试代码能明确看出效果而已。
我现在有点搞懂了,你这是对现代GC的工作方式不是很理解。
现代GC哪有那么快就回收的,都是在后台默默开个线程干活,甚至.net中如果进程结束那最后一次GC都不执行了(由操作系统直接善后,java没测试过是否有这个行为)
如果想要在进程结束后立即释放进程相关资源,那就自己dispose啊
对于没有任何引用的孤对象,java和.net(还有go,node……)都只能保证在未来的某个时刻进行回收,想精细回收的玩c、c++、rust去吧
@Nyarlathotep: 不要着急,我给你细细道来:
1.以上哥们的代码,调用了垃圾回收器GC.Collect(),但最后有一个对象没有释放内存,亲没解答
2.对象回收在什么时候在什么是否发生,Jeffery Ritcher可以告诉你.
3.Java的垃圾回收以及ThreadLocal均没有问题.
4.我的问题是:如何设计一个一个线程内对象复用功能,线程结束时能自动销毁这个对象
@[秦时明月]: 我抓狂了……都说了多少遍GC.Collect()是异步执行的,你丫的GC没执行完自然对象还活着好好的啊
前面已经说的很清楚了,有实现IDisposable接口就调用,然后把引用的地方设置成null,后面自然就释放了
两家都是实现的很好的,你非要纠结一个小控制台一瞬间执行完后对象有没有被GC销毁,能看出什么嘛
然后如果你能确定gc具体发生的时间,那就是实时系统了,java倒是有个实时系统分支,你确定你见过?
顺便Jeffery Ritche哪位?世界这么大,我不认识的人多了去了
http://stackoverflow.com/questions/1927229/why-are-rtos-coded-only-in-c
这个so讨论了为啥gc的时间就是那么不确定
@Nyarlathotep: 哥们,Jeffery Ritcher不知道就好像干JAVA不知道高斯林.GC异步执行没错,但何时回收这是可以知道的(更何况你GC.Collect等上几个小时还不回收,这明显会成软件问题).楼主在讲如何设计一个复用功能,人家在说事请,你在激动.....
@hellozmh: 首先,我不是搞java的,其次,我记住Anders Hejlsberg大神竟然是因为typescript,
第三,我说的不确定是指,对于不特定gc实现,你没法说清楚是具体多久之后(几秒,几毫秒之后),但是肯定会发生(比如某个事件后,或者多少垃圾后……看实现了,再看了下楼主博客的那个demo,鬼知道是不是内存压力不够gc懒得回收,具体得看文档,不过深究这点细节没什么大的意义),所以日常使用中,除非做性能优化,不会特意纠结这个问题,更不会认为这就算Memory leaking了。
我从头到尾都在说,楼主可以安心使用ThreadLocal,不用担心微软就智障的犯这么低级的错误,楼主的测试有问题是因为没有考虑gc的异步性
至于你认为我语气激动,其实就是刚开始看题目,楼主讨论这个问题,还以为至少基本功是扎实的,然而从头到尾就是各种质疑,没什么建设性,自然感觉蛋疼
@hellozmh: 再补充下,除非实时版的java,剩下的都只能说差不多某个时候(时间点、事件……)gc会怎样(不同collector之间还差别明显)这是本身工作原理,如果demo设计不合理就难以体现实际运行时的行为。就demo那点内存压力,那点时间,不手动gc想要看到对象释放效果,一开始从逻辑上就很难保证嘛。
@Nyarlathotep: 提示,我下面的结贴.
经过和楼主交流后,我决定来结贴
首先,ThreadLocal的设计有缺陷(不友好)或者说文档缺失,即便对其Dipose(),对其所有引用设置为null,如果
不调用CurrentThread.ThreadLocal.Value=null;就会出现内存泄漏;
其次,以上的一个内存不释放的原因在于Debug模式的编译决定的,为了给调试器使用会有此情况(Jeffery Ritcher书中提到);
再次,ThreadLocal内存泄漏类似问题连接:
http://stackoverflow.com/questions/33172615/memory-leak-when-threadlocalt-is-used-in-cyclic-graph
---Microsoft's David Kean confirmed that this actually is a bug.
最后,老外给出了一个建议如何线程内的对象复用.
|
I'd rather recommend you to redesign your system, but if you really want it, then you can try to abstract msdn.microsoft.com/en-us/library/windows/desktop/… taking into account that stackoverflow.com/questions/301054/…. But I am not quite sure that it will work in all cases, especially with thread pool threads. – Eugene Podskal 2 days ago
|
有情提示:哥们不要激动,人们之间智商都差不多.
1. 我前面强调的是:ThreadLocal内的所有值最终都会随着线程一起销毁
2. https://referencesource.microsoft.com/#mscorlib/system/threading/ThreadLocal.cs,80de775120278afd,references 这里明确表明,至少.net4.6.2上Dispose时内部已经赋值null了。你可以比对下java/.net两家的代码,其实设计非常接近
3. 至少Debug模式对我直接运行你的代码,并且成功看到7的对象的析构函数被执行没有影响(直接vs debug模式开始执行,不调试,就是最后一个对象的析构要等一段时间)
4. http://stackoverflow.com/questions/33172615/memory-leak-when-threadlocalt-is-used-in-cyclic-graph 这个是由于GC计算循环依赖时出错产生的bug,只要达到触发条件就会发生,本质上和ThreadLocal也没有关系。4.6刚出来时尾递归优化还导致变量丢失呢,这难道可以算是用了递归的错?
4. 线程池中的线程本身就不好说什么时候死,当然只能手工释放保稳妥了
回头看了下楼主的问题,怕ThreadLocal不可靠就上ThreadStatic(就是ThreadStatic被嫌弃太重才有了ThreadLocal)嘛,关键是为什么一定要赖ThreadLocal呢?就算你骂.net GC实现sb我也没意见
很明显,在JAVA中threadLocal直接是Thread的成员,当然随着thread这个宿主而存在.
但.NET中对于ThreadLocal<T>的设计明显独立于Thread之外(当然你可以说内部有关联)
这是问题那链接里开头写的,可是这个理解本身明显是错的。其实看看后来我贴的微调后的demo,明显GC很快就好了,我反而怀疑是线程调度问题导致原先的代码GC慢了一拍(GC在另一线程执行)。