首页 新闻 会员 周边 捐助

微软的ThreadLocal<T>的是否有设计问题,求解惑?--莫非此题无人能解?

0
悬赏园豆:200 [已关闭问题] 关闭于 2017-01-04 18:14

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>

只要能实现此功能即可

[秦时明月]的主页 [秦时明月] | 小虾三级 | 园豆:738
提问于:2016-12-31 12:06
< >
分享
所有回答(5)
0

http://stackoverflow.com/questions/5760347/memory-concerns-and-threadlocalt

 

多动手,多Google

XiaoFaye | 园豆:3087 (老鸟四级) | 2016-12-31 13:45

 你觉得你再解决问题吗?发一个链接知道里面在说啥吗?知道如何解决这个问题吗?

支持(0) 反对(0) [秦时明月] | 园豆:738 (小虾三级) | 2016-12-31 18:00

@[秦时明月]: 要不我把代码也给你写了,把你的工资也领了,这样算解决问题了吧?

支持(0) 反对(0) XiaoFaye | 园豆:3087 (老鸟四级) | 2017-01-01 06:20
0

这个需要自己调用Dispose释放掉吧?

TechMoeTiger | 园豆:36 (初学一级) | 2017-01-01 23:29
0

你把ThreadLocal<T>去掉,你会发现,内存一样没有被释放掉,这和ThreadLocal<T>没有关系。

这是.Net垃圾回收机制和重写Finalize方法的原因,建议你看看垃圾回收机制和Finalize的原理。

ArthurLi | 园豆:686 (小虾三级) | 2017-01-02 11:57

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.

能做到这个也行

 

支持(0) 反对(0) [秦时明月] | 园豆:738 (小虾三级) | 2017-01-02 14:48

@[秦时明月]: 把~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;
        }
    }
支持(0) 反对(0) ArthurLi | 园豆:686 (小虾三级) | 2017-01-02 16:22
0

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回收完成了)

不过在这个源码都公开的年代,如果不能确认就提问啊,写个博客误导他人的话多不好?

 

Nyarlathotep | 园豆:271 (菜鸟二级) | 2017-01-03 14:21

人家是在说如何设计这个功能:

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.

线程什么时候结束没有人知道

支持(0) 反对(0) hellozmh | 园豆:29 (初学一级) | 2017-01-03 17:02

我给你一个版本,用你的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个对象内存没有释放

 

 

支持(0) 反对(0) hellozmh | 园豆:29 (初学一级) | 2017-01-03 17:19

@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);
        }
    }
}
支持(0) 反对(0) Nyarlathotep | 园豆:271 (菜鸟二级) | 2017-01-03 18:31

@Nyarlathotep: 知道上面哥们一个内存不释放原因吗

支持(0) 反对(0) [秦时明月] | 园豆:738 (小虾三级) | 2017-01-04 07:52

@[秦时明月]: 垃圾回收是在另一个线程进行的,如果没有等待GC完成,就急匆匆把窗口关掉,自然看不到啦。可以查查GC类的msdn,里面有各种等待方法。

支持(0) 反对(0) Nyarlathotep | 园豆:271 (菜鸟二级) | 2017-01-04 08:28

@Nyarlathotep: 你等半个小时试试

支持(0) 反对(0) [秦时明月] | 园豆:738 (小虾三级) | 2017-01-04 08:30

@[秦时明月]: 不需要啊,没仔细测量,我机子上他那个代码多等1-2s最后一笔就析构了

GC.GetTotalMemory(true)会直接执行GC并等待完成,用我调整后的代码立即就看得出效果了

其实你怀疑微软sb的话,最好的方式是去看源码,后来我补充了,两家的实现大的逻辑上是一样的。

反证法:如果微软这么sb,他们怎么通过自家的QA的?

支持(0) 反对(0) Nyarlathotep | 园豆:271 (菜鸟二级) | 2017-01-04 08:36

@Nyarlathotep: 你没有理解我的意图,我是想设计一个线程内对象复用功能,线程结束时能自动销毁这个对象。treadlocal如果要你这样才能释放,我是不是要哭

支持(0) 反对(0) [秦时明月] | 园豆:738 (小虾三级) | 2017-01-04 08:41

@[秦时明月]: 两家都只能保证线程销毁后的某个时刻(GC把线程回收后)才顺手把线程相关对象回收掉。

调用GC只是让测试代码能明确看出效果而已。

我现在有点搞懂了,你这是对现代GC的工作方式不是很理解。

现代GC哪有那么快就回收的,都是在后台默默开个线程干活,甚至.net中如果进程结束那最后一次GC都不执行了(由操作系统直接善后,java没测试过是否有这个行为)

如果想要在进程结束后立即释放进程相关资源,那就自己dispose啊

对于没有任何引用的孤对象,java和.net(还有go,node……)都只能保证在未来的某个时刻进行回收,想精细回收的玩c、c++、rust去吧

支持(0) 反对(0) Nyarlathotep | 园豆:271 (菜鸟二级) | 2017-01-04 08:51

@Nyarlathotep: 不要着急,我给你细细道来:

1.以上哥们的代码,调用了垃圾回收器GC.Collect(),但最后有一个对象没有释放内存,亲没解答

2.对象回收在什么时候在什么是否发生,Jeffery Ritcher可以告诉你.

3.Java的垃圾回收以及ThreadLocal均没有问题.

4.我的问题是:如何设计一个一个线程内对象复用功能,线程结束时能自动销毁这个对象

支持(0) 反对(0) [秦时明月] | 园豆:738 (小虾三级) | 2017-01-04 09:04

@[秦时明月]: 我抓狂了……都说了多少遍GC.Collect()是异步执行的,你丫的GC没执行完自然对象还活着好好的啊

前面已经说的很清楚了,有实现IDisposable接口就调用,然后把引用的地方设置成null,后面自然就释放了

两家都是实现的很好的,你非要纠结一个小控制台一瞬间执行完后对象有没有被GC销毁,能看出什么嘛

然后如果你能确定gc具体发生的时间,那就是实时系统了,java倒是有个实时系统分支,你确定你见过?

顺便Jeffery Ritche哪位?世界这么大,我不认识的人多了去了

http://stackoverflow.com/questions/1927229/why-are-rtos-coded-only-in-c

这个so讨论了为啥gc的时间就是那么不确定

支持(0) 反对(0) Nyarlathotep | 园豆:271 (菜鸟二级) | 2017-01-04 09:14

@Nyarlathotep: 哥们,Jeffery Ritcher不知道就好像干JAVA不知道高斯林.GC异步执行没错,但何时回收这是可以知道的(更何况你GC.Collect等上几个小时还不回收,这明显会成软件问题).楼主在讲如何设计一个复用功能,人家在说事请,你在激动.....

支持(0) 反对(0) hellozmh | 园豆:29 (初学一级) | 2017-01-04 09:32

@hellozmh: 首先,我不是搞java的,其次,我记住Anders Hejlsberg大神竟然是因为typescript,

第三,我说的不确定是指,对于不特定gc实现,你没法说清楚是具体多久之后(几秒,几毫秒之后),但是肯定会发生(比如某个事件后,或者多少垃圾后……看实现了,再看了下楼主博客的那个demo,鬼知道是不是内存压力不够gc懒得回收,具体得看文档,不过深究这点细节没什么大的意义),所以日常使用中,除非做性能优化,不会特意纠结这个问题,更不会认为这就算Memory leaking了。

我从头到尾都在说,楼主可以安心使用ThreadLocal,不用担心微软就智障的犯这么低级的错误,楼主的测试有问题是因为没有考虑gc的异步性

至于你认为我语气激动,其实就是刚开始看题目,楼主讨论这个问题,还以为至少基本功是扎实的,然而从头到尾就是各种质疑,没什么建设性,自然感觉蛋疼

支持(0) 反对(0) Nyarlathotep | 园豆:271 (菜鸟二级) | 2017-01-04 09:45

@hellozmh: 再补充下,除非实时版的java,剩下的都只能说差不多某个时候(时间点、事件……)gc会怎样(不同collector之间还差别明显)这是本身工作原理,如果demo设计不合理就难以体现实际运行时的行为。就demo那点内存压力,那点时间,不手动gc想要看到对象释放效果,一开始从逻辑上就很难保证嘛。

支持(0) 反对(0) Nyarlathotep | 园豆:271 (菜鸟二级) | 2017-01-04 09:52

@Nyarlathotep: 提示,我下面的结贴.

支持(0) 反对(0) hellozmh | 园豆:29 (初学一级) | 2017-01-04 12:38
0

经过和楼主交流后,我决定来结贴

首先,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

有情提示:哥们不要激动,人们之间智商都差不多.

hellozmh | 园豆:29 (初学一级) | 2017-01-04 12:38

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在另一线程执行)。

支持(0) 反对(0) Nyarlathotep | 园豆:271 (菜鸟二级) | 2017-01-04 13:31
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册