关于GC和析构函数的一个趣题
这个有趣的问题感谢装配脑袋友情提供。
请看如下代码:
public class Dummy
{
public static Dummy Instance;
public int X = ; ~Dummy()
{
Instance = this;
}
}
通过如下代码进行调用(输出日志的地方我稍作调整):
Task.Run(() =>
{
var d = new Dummy();
d = null;
GC.Collect();
GC.WaitForFullGCComplete(); }).Wait(); var isNull = Dummy.Instance == null;
Console.WriteLine(isNull);
if (false == isNull)
{
Console.WriteLine(Dummy.Instance.X);
}
else
{
Console.WriteLine("Oh no!Dummy.Instance is null.");
}
问题:上述输出的Instance == null是True还是False?
此处您可以先停止阅读下面的分析,想一想您的回答会是什么呢?
首先这个题目一看就是那种明知有坑让你钻进去但是你还可能必须先钻进去的感觉。尤其是Task、GC、静态字段、实例字段,析构函数这么多东西混在一起的时候,一看就和多线程有关系,相当具有迷惑性,对不对?
我第一次看到的时候,认为Task运行起来进行GC回收然后Wait等到任务结束,变量d指向的对象因为GC.WaitForFullGCComplete()这一行,应该已经被垃圾回收成功,执行析构函数的时候,静态变量Instance指向的当前对象this(也就是变量d一开始所指向的引用对象)应该是null,那么Instance==null肯定返回True。或者输出应该总是一个确定值。
但是实际运行效果并不总是如此,请注意,经我个人多次实验,循环多次(大于等于1小于等于50000),输出True和False的次数是不确定的,但是True的出现概率明显多过False,False的总数好像总是1到10个之间。
为了防止C#编译器的某些优化,分别对比Release和Debug下的运行效果,结果还是一样的。
然后实在有点想不通为什么输出的结果有两种。循环实验了下如下代码,没有Task干扰,但效果和有Task运行的也是差不多,都有True或False输出,也就是说不用Task顺序执行GC代码也是有不同的输出。
var d = new Dummy();
d = null;
GC.Collect();
GC.WaitForFullGCComplete(); var isNull = Dummy.Instance == null;
Console.WriteLine(isNull);
if (false == isNull)
{
Console.WriteLine(Dummy.Instance.X);
}
else
{
Console.WriteLine("Oh no!Dummy.Instance is null.");
}
最近正好我在重新学习GC,不久前又刚刚总结了一下GC知识,想起析构函数终结上有“延长”垃圾对象生命周期的情况,但也说不通。又想过是否析构函数对静态字段进行了特殊优化,比如Instance赋值后导致GC回收策略自动调整,将G0代调整为G1代,又或者析构函数执行时this没有自动回收,也就是静态字段赋值有线程安全的控制导致先将this赋值给Instance然后this等Instance被回收才置为空,但因为Instance是静态字段,是GC的根,所以,嗯?学了很多理论,发现实践起来依然不是那么回事。
实在想不出根本原因,请教了下脑袋,他简要回答是“实际造成竞态条件的是Finalizer执行的线程。。”。
析构函数竞态条件,Finalizer,线程?哦,wait,等等,主线程、当前Task运行的线程池托管线程、GC线程、Finalizer线程,产生了竞态条件的是几种线程之间(比如GC线程和Finalizer线程)还是相同类型的线程之间(比如Finalizer线程和Finalizer线程)产生竞争呢?
顺着这个思路,把线程ID打印出来对比一下不就有结论了吗?
严重声明:这里我也不清楚执行析构函数 ~Dummy()时当前线程是否就是Finalizer线程,看书上好像是这个意思,但没给出代码,本文先暂时以Finalizer线程这么命名这个线程吧。如果您知道如何正确取得GC线程和Finalizer线程请不另赐教。
立即动手,调整了一下代码,多打印出一些日志,虽然打印出来的日志有点凌乱,但是终于可以肯定Task和析构函数执行的托管线程ID的不同,而析构函数里面的托管线程的线程ID总是一样。
public class Dummy
{
public static Dummy Instance;
public int X = ; public static ConcurrentBag<int> threadIDBag = new ConcurrentBag<int>(); ~Dummy()
{
var threadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Destructor CurrentContext ThredID:{0}", threadId);
if (threadIDBag.Contains(threadId) == false)
{
threadIDBag.Add(threadId);
} Instance = this; //Console.WriteLine("Destructor===Instance is null:{0}", Instance == null);
}
}
Dummy
调用代码如下:
static void Main(string[] args)
{
var counter = ; //statistics Dummy Instance is not null count
var testCnt = ;// 50000; //执行task个数
while (testCnt > )
{
testCnt--; Task.Run(() =>
{
var d = new Dummy();
d = null;
GC.Collect();
GC.WaitForFullGCComplete(); Console.WriteLine("Task CurrentContext ThredID:{0}", Thread.CurrentThread.ManagedThreadId); }).Wait(); var isNull = Dummy.Instance == null;
Console.WriteLine(isNull);
if (false == isNull)
{
Console.WriteLine(Dummy.Instance.X);
counter++;
}
else
{
Console.WriteLine("Oh no!Dummy Instance is null.");
} Console.WriteLine("========================"); } Thread.Sleep();
Console.WriteLine("End Task......");
Console.WriteLine("Dummy Instance is not null counter:{0}", counter); Console.WriteLine("Finalizer ThreadID Count:{0}", Dummy.threadIDBag.Count); //此处输出为1 Console.ReadKey();
}
RunTask
到这里我敢肯定装配脑袋说的“竞态条件”肯定不是Finalizer线程和Finalizer线程之间产生的竞态,也不是GC线程和Finalizer线程之间产生的竞态。
又因为脑袋说过Task运行后进行了Wait,应该也不是Task运行所分配的托管线程和Finalizer线程之间产生的竞态。
所以,应该是执行调用线程(本例即执行完Task后调用Console.WriteLine()的主线程)和Finalizer线程之间产生了线程竞争。
到这里能够得出的结论,我认为可能说得通的解释就是,应用程序执行线程MainThread运行代码Console.WriteLine(Dummy.Instance == null)的时候,析构函数线程FinalizerThread可能刚要执行但是还没有运行Instance=this这行代码,这样Dummy.Instance就不是空,输出就是False。
简单理解就是Finalizer线程的执行不确定性导致输出有不同效果。
不知各位以为然否?
补充三个问题:
1、如果将GC.WaitForFullGCComplete()改为GC.WaitForPendingFinalizers()输出效果如何?
2、如Dummy继承自IDisposable,执行Dispose()方法的线程ID是什么?
3、如何直接而正确取得GC线程和Finalizer线程?它们都是线程池中的托管线程吗?
多看多想再勤动手,实践出真知。
参考:
<<CLR Via C#>>
http://www.cppblog.com/Solstice/archive/2010/01/28/dtor_meets_threads.html
http://msdn.microsoft.com/zh-cn/library/system.idisposable.dispose%28v=vs.110%29.aspx
http://blogs.msdn.com/b/dotnet/archive/2014/11/12/net-core-is-open-source.aspx
关于GC和析构函数的一个趣题的更多相关文章
- [转]趣题:一个n位数平均有多少个单调区间?---- From Matrix67
考虑这么一个 14 位数 02565413989732 ,如图所示,它的数字先逐渐变大,然后开始变小,再变大,再变小,再变大,再变小.我们就说,它一共包含了 6 个单调区间.我们的问题就是:一个 n ...
- 【C#】GC和析构函数(Finalize 方法)
析构函数: (来自百度百科)析构函数(destructor) 与构造函数相反,当对象脱离其作用域时(例如对象所在的函数已调用完毕),系统自动执行析构函数.析构函数往往用来做"清理善后&quo ...
- 关于php析构函数的一个有趣问题
随着面向对象编程的普遍展开,面向对象展现了其中很多有趣的问题.相信很多初学者学习php面向对象时会接触两个函数,构造函数与析构函数.构造函数似乎用的更多,析构函数用的较少(相对初学者有限编程经验而言, ...
- C 解决百度知道的一个高中题
前言 今天看见一道百度知道上提问,是这样的. 仔细算了一下, 花了30min.才整出来了,估计现在回去参加高考,数学及格都悬.有时候想做这样的题有什么用, 学这些东西有什么意义,在这种方面浪费时间有什 ...
- POJ 2260(ZOJ 1949) Error Correction 一个水题
Description A boolean matrix has the parity property when each row and each column has an even sum, ...
- c++学习笔记4,调用派生类的顺序构造和析构函数(一个)
测试源代码: //測试派生类的构造函数的调用顺序何时调用 //Fedora20 gcc version=4.8.2 #include <iostream> using namespace ...
- 一个JAVA题引发的思考
转载自:http://www.cnblogs.com/heshan664754022/archive/2013/03/24/2979495.html 十年半山 今天在论坛闲逛的时候发现了一个很有趣的题 ...
- ACM/ICPC Moscow Prefinal 2019 趣题记录
### Day1: ### **Problem C:** 设$k_i$为$[A, B]$中二进制第$i$位是1的数的个数. 给出$k_0 \cdots k_{63}$, 求出$[A, B]$ ...
- 代数&数论趣题集萃
暑假总不能只学习平面几何.所以这里也收集一些有趣的代数题或数论题,同时记下解法的一些提示.给未来的自己复习参考用. 多图片预警(请注意流量) 目录: Part 0:其他(8) Part 1:不等式(1 ...
随机推荐
- linux线程
线程:轻量级进程,在资源.数据方面不需要进行复制 不间断地跟踪指令执行的路径被称为执行路线 进程的结构:task_struck:地址空间 线程:轻量级的进程 在同一个进程中创建的线程,在共享进程的地址 ...
- dos学习
>>>>>>>>>> arp-a:查看路由缓存表,所有的IP都在这里. ping <ip地址(例:192.168.x.x)>:查 ...
- iTerm 2 && Oh My Zsh
一年前,在搞终端的时候偶然一次机会,让我看到了各种强大的DIY界面,这让我很想去自己搞一个.于是在网上不断的寻找资源,也请教了大多数朋友.最终以失败告终.最近,本人又突然想起当时这件事,于是,决定边做 ...
- Ice-E(Embedded Internet Communications Engine)移植到s3c2440A(arm9)linux(2.6.12)上的
2009-03-26 18:31:31 原文链接 1.前言 ICE-E是ICE在嵌入式上运行的一个版本,与ICE比较如下: Category Ice 3.3.0 Ice-E 1.3.0 Thread ...
- 图片采用base64压缩,可以以字符串的形式传送base64给服务端转存为图片
(function () { var coverImage = document.querySelector('<div id="coverImage">file< ...
- 2_MVC+EF+Autofac(dbfirst)轻型项目框架_用户权限验证
前言 接上面两篇 0_MVC+EF+Autofac(dbfirst)轻型项目框架_基本框架 与 1_MVC+EF+Autofac(dbfirst)轻型项目框架_core层(以登陆为例) .在第一篇中介 ...
- 第一章 DeepLab的创作动机
这一段时间一直在做深度学习方面的研究,目前市场上的深度学习工具主要分为两大块.一块是基于Python语言的theano:另一块是可以在多个语言上使用并能够在GPU和CPU之间随意切换的Caffe.但是 ...
- .net core Jwt 添加
Jwt 已经成为跨平台身份验证通用方案,如不了解请关注:https://jwt.io/. 为了和微软其他验证模块有个比较好的衔接,项目中采用了微软开发的jwt组件: System.IdentityMo ...
- [LintCode] Linked List Cycle 单链表中的环
Given a linked list, determine if it has a cycle in it. ExampleGiven -21->10->4->5, tail co ...
- 【hihoCoder】1121:二分图一·二分图判定
题目 http://hihocoder.com/problemset/problem/1121 无向图上有N个点,两两之间可以有连线,共有M条连线. 如果对所有点进行涂色(白/黑),判定是否存 ...