由StreamWriter.WriteLine 引发对C#多线程的深入思考(一)

http://blog.csdn.net/nndtdx/article/details/6789810首先,StreamWriter线程安全么?答:StreamWriter的构造以及StreamWriter.WriteLine(string)都是非线程安全的我们封装两个写日志的方法。底层都是由StreamWriter.writeline来实现.一个加锁,一

大家好,又见面了,我是你们的朋友全栈君。

http://blog.csdn.net/nndtdx/article/details/6789810

首先,StreamWriter线程安全么?

答:StreamWriter 的构造以及StreamWriter.WriteLine(string)都是非线程安全的

我们封装两个写日志的方法。

底层都是由StreamWriter.writeline来实现.一个加锁,一个不加锁。将加锁的那个命名为safewritelog,另一个命名为unsafeWritelog.然后利用两个循环。不停的分别创建个线程,去写日志。测试看哪个会出现写异常。代码如下:

[csharp] 
view plain
 copy

 print
?

  1. namespace ThreadWriteLog  
  2. {  
  3.     class Program  
  4.     {  
  5.   
  6.         private static object ob = “哟内容!!”;  
  7.   
  8.         static void Main(string[] args)  
  9.         {  
  10.   
  11.   
  12.             for (int i = 0; i < 10; i++)  
  13.             {  
  14.                 Thread wrtieThread = new Thread(SafyWriteLog);  
  15.                 wrtieThread.Name = “线程–“ + i;  
  16.                 string content = “这是” + wrtieThread.Name + “的内容Y”;  
  17.                 wrtieThread.Start(content);  
  18.             }  
  19.   
  20.             for (int i = 0; i < 10; i++)  
  21.             {  
  22.                 Thread wrtieThread = new Thread(UnSafyWriteLog);  
  23.                 wrtieThread.Name = “线程¨¬–“ + i;  
  24.                 string content = “这是” + wrtieThread.Name + “的内容Y”;  
  25.                 wrtieThread.Start(content);  
  26.             }  
  27.             Console.WriteLine(“结束”);  
  28.             Console.Read();  
  29.         }  
  30.   
  31.   
  32.         
  33.   
  34.           
  35.         public  static void SafyWriteLog(object  content)  
  36.         {  
  37.             string path = @“C:\SafeLog.txt”;  
  38.             lock (ob)  
  39.             {  
  40.                 StreamWriter sw = File.AppendText(path);  
  41.                 sw.WriteLine(content.ToString());  
  42.                 sw.Close();  
  43.             }  
  44.         }  
  45.   
  46.         public static void UnSafyWriteLog(object content)  
  47.         {  
  48.             string path = @“C:\UnSafeLog.txt”;  
  49.             StreamWriter sw = File.AppendText(path);  
  50.             sw.WriteLine(content.ToString());  
  51.             sw.Close();  
  52.         }  
  53.     }  
  54. }  




运行后,第一个for循环顺利结束,文件中显示 0-9进程没有问题。

这是线程–0的内容

这是线程–1的内容

这是线程–2的内容

这是线程–5的内容

这是线程–3的内容

这是线程–4的内容

这是线程–6的内容

这是线程–7的内容

这是线程–8的内容

这是线程–9的内容

也符合线程的概念,随着系统的随机调度而运行。

而第二个for循环没有正常完成,抛出异常

 

 

[csharp] 
view plain
 copy

 print
?

  1. 未处理的异常: 未处理的异常: 未处理的异常: 未处理的异常: 未处理的异常: 未处理的异  
  2. 常:       System.IO.IOException: 文件“C:\UnSafeLog.txt”正由另一进程使用,因此  
  3. 该进程无法访问该文件。  
  4.    在 System.IO.__Error.WinIOError(Int32 errorCode, StringmaybeFullPath)  
  5.    在 System.IO.FileStream.Init(String path, FileMode mode,FileAccess access, I  
  6. nt32rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions o  
  7. ptions,SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy)  
  8.    在 System.IO.FileStream..ctor(String path, FileMode mode,FileAccess access,  
  9. FileShareshare, Int32 bufferSize, FileOptions options)  
  10.    在 System.IO.StreamWriter.CreateFile(String path, Booleanappend)  
  11.    在 System.IO.StreamWriter..ctor(String path, Booleanappend, Encoding encodin  
  12. g, Int32bufferSize)  
  13.    在 System.IO.StreamWriter..ctor(String path, Booleanappend)  
  14.    在 System.IO.File.AppendText(Stringpath)  
  15.    在 caTestProj.Program.UnSafyWriteLog(Object content) 位置 F:\ASP.NET\MyCode\c  
  16. aTestProj\caTestProj\Program.cs:行号 51  
  17.    在 System.Threading.ThreadHelper.ThreadStart_Context(Objectstate)  
  18.    在 System.Threading.ExecutionContext.Run(ExecutionContextexecutionContext, C  
  19. ontextCallbackcallback, Object state)  
  20.    在 System.Threading.ThreadHelper.ThreadStart(Objectobj)System.IO.IOException  
  21. : 文件“C:\UnSafeLog.txt”正由另一进程使用,因此该进程无法访问该文件。  
  22.    在 System.IO.__Error.WinIOError(Int32 errorCode, StringmaybeFullPath)  




 

正常分析理解,

StreamWriter.WriteLine方法本身没有线程同步方法,多线程写日志时(注意这里,我们不同的线程使用的是不同的StreamWriter),多个线程同时访问文件,出现异常。

但是 确实是WriteLine出错了么?

从堆栈跟踪来看,错误出现在Thread线程回调UnSafyWriteLog方法出现错误,即执行AppendText时出错。

在到里边看,构造StreamWriter对象出错-à FileStream对象构造出错– 调用FileStream.Init出错,最后到了win32函数winIoError.也就是构造FileStream对象时出错。我们很明白肯定一个共享写的问题了。

那么,可以断定,问题在于,调用File.APpendTest时,会构造StreamWriter,而这个StreamWriter是独占式的

由于该文件已被另一个线程访问,所以StreamWriter构造出现异常,

而并不是在StreamWriter.WriteLine上出的错误。

对于SafeWritelog,我们对Streamwriter的构造以及write都加了锁,也就是说,每次构造streamWriter的时候,还是Writeline的时候,我们都保证了有唯一的对象对磁盘文件(或者缓存)进行操作。

那如果我用同一个StreamWriter呢?也就是说,Writeline本身是不是多线程安全的?

现在我们使用同一个Streamwriter,测试writeline的线程安全特性。

也就是说,如果同时有两个线程同时调用Wtriteline方法,出现异常,则说明非线程安全可没有异常,说明线程安全。

代码如下

[csharp] 
view plain
 copy

 print
?

  1. class Program  
  2. {  
  3.     static object   ob=new object();  
  4.     static string path = @“C:\UnSafeLog.txt”;  
  5.     private static StreamWriter sw2;  
  6.   
  7.     static void Main(string[] args)  
  8.     {  
  9.         for (int i = 0; i < 10; i++)  
  10.         {  
  11.             Thread wrtieThread = new Thread(SafyWriteLog);  
  12.             wrtieThread.Name = “线程–“ + i;  
  13.             string content = “这是” + wrtieThread.Name + “的内容”;  
  14.             wrtieThread.Start(content);  
  15.         }  
  16.        sw2 = File.AppendText(path);  
  17.         for (int i = 0; i < 10; i++)  
  18.         {  
  19.             Thread wrtieThread = new Thread(UnSafyWriteLog);  
  20.             wrtieThread.Name = “线程–“ + i;  
  21.             string content = “这是” + wrtieThread.Name + “的内容Y”;  
  22.             wrtieThread.Start(content);  
  23.         }  
  24.          sw2.Close();  
  25.         Console.WriteLine(“结束”);  
  26.         Console.Read();  
  27.   
  28.     }  
  29.   
  30.   
  31.     public static void SafyWriteLog(object content)  
  32.     {  
  33.         string path = @“C:\SafeLog.txt”;  
  34.         lock (ob)  
  35.         {  
  36.             StreamWriter sw = File.AppendText(path);  
  37.             sw.WriteLine(content.ToString());  
  38.             sw.Close();  
  39.         }  
  40.     }  
  41.   
  42.     public static void UnSafyWriteLog(object content)  
  43.     {  
  44.   
  45.         sw2.WriteLine(content.ToString());  
  46.   
  47.     }  
  48.   
  49. }  




运行后,貌似没出现什么问题。两个for循环都正常执行完毕了。

而且日志文件中的记录也是按顺序来的,没有出现日志文字错乱现象。

这是线程–0的内容

这是线程–1的内容

这是线程–2的内容

这是线程–3的内容

这是线程–4的内容

这是线程–5的内容

这是线程–6的内容

这是线程–7的内容

这是线程–8的内容

这是线程–9的内容

但真的是这样的么?构造是非线程安全的,而writeline是线程安全的??不可能的。

仔细考虑,只有在某一个线程阻塞在WriteLine的时候,另一个线程也访问该方法的时候,才会出现多线程写的情况。

那么,ok,我们加大一次文字的写入量,使其阻塞在WriteLine这里

现在,我们构造更加简单的场景,创建两个线程,一次写入大量日志,使用同一个StreamWriter对象。如果writeline方法是非线程安全的,那么肯定会出现异常。

代码如下:


[csharp] 
view plain
 copy

 print
?

  1. class Program  
  2. {  
  3.     public static StreamWriter sw = new StreamWriter(“C:\\threadlog.txt”true);  
  4.     private static string path = @“C:\Users\cjt.IT\Desktop\20110807\S20110807031902.info”;//大文件内容10M级  
  5.     private Thread t1;  
  6.     private Thread t2;  
  7.   
  8.     static void Main(string[] args)  
  9.     {  
  10.         Program p=new Program();  
  11.         p.Test();  
  12.     }  
  13.   
  14.   
  15.     public void Test()  
  16.     {  
  17.         StreamReader sr = new StreamReader(path);  
  18.         string content = sr.ReadToEnd();  
  19.         BeginWrite1(content);  
  20.         BeginWrite2(content);  
  21.         sr.Close();  
  22.         //t1.Join();  
  23.         //sw.Close();  
  24.     }  
  25.   
  26.     private void BeginWrite1(string content)  
  27.     {  
  28.         t1 = new Thread(WriteLog);  
  29.         t1.Start(“———线程1Begin——–“ + Environment.NewLine + content + “———线程1 End——–“ + Environment.NewLine);  
  30.     }  
  31.   
  32.     private void BeginWrite2(string content)  
  33.     {  
  34.         t2 = new Thread(WriteLog);  
  35.         t2.Start(“———线程2Begin——–“ + Environment.NewLine + content + “———线程2End——–“ + Environment.NewLine);  
  36.     }  
  37.   
  38.     private void WriteLog(object content)  
  39.     {  
  40.         sw.WriteLine(content.ToString());//内容过多线程会阻塞在该处  
  41.         sw.Flush();//这a样能够保证缓o冲区内的数据全部写磁盘¨¬  
  42.     }  
  43.   
  44. }  


运行之后,立即出现异常。


[csharp] 
view plain
 copy

 print
?

  1. 未处理的异常:  System.IndexOutOfRangeException: 在复制内存时检测到可能的 I/O 争  
  2. 用条件。默认情况下,I/O 包不是线程安全的。在多线程应用程序中,必须以线程安全方式  
  3. (如 TextReader 或 TextWriter 的 Synchronized 方法返回的线程安全包装)访问流。这也  
  4. 适用于 StreamWriter 和 StreamReader 这样的类。  
  5.    在 System.Buffer.InternalBlockCopy(Array src, Int32 srcOffset, Array dst, Int  
  6. 32 dstOffset, Int32 count)  
  7.    在 System.IO.StreamWriter.Write(Char[] buffer, Int32 index, Int32 count)  
  8.    在 System.IO.TextWriter.WriteLine(String value)  
  9.    在 caTestProj.Program.WriteLog(Object content) 位置 F:\ASP.NET\MyCode\caTestP  
  10. roj\caTestProj\Program.cs:行号 51  
  11.    在 System.Threading.ThreadHelper.ThreadStart_Context(Object state)  
  12.    在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, C  
  13. ontextCallback callback, Object state)  
  14.    在 System.Threading.ThreadHelper.ThreadStart(Object obj)  
  15. 请按任意键继续. . .  


从堆栈跟踪来看:

在调用TextWriter.WriteLine(Streamwriter继承于此)时出错,系统要进行内存拷贝,出现I/O争用。也就是说,线程不安全本质是由(至少该例子中是由I/o争用导致的)。两者都要将自己的内容拷贝到磁盘上,显然要出错。writeline方法缺少同步机制,抛出异常。

这里是日志内容一部分

由StreamWriter.WriteLine 引发对C#多线程的深入思考(一)



可以看出来中间有个线程2begin,可以肯定,在这里,线程1暂时阻塞,然后线程2开始写,也就是说从这里开始,日志文件就开始混乱了,因为1已经阻塞了,这里没有出现异常。

日志文件结束

由StreamWriter.WriteLine 引发对C#多线程的深入思考(一)


中间并没有出现线程2end的标记,说明,线程2中间也阻塞了。这是启动了线程1,造成中间这个界限丢失了。也有可能从线程2begin这一段,线程1和线程2就是混着写的。直到他们两个突然决定同时写,系统出现I/O争用错误,抛出异常。

 

如果我对其加锁呢?

显然我们如果控制了同时writelien的只有一个线程。那么写日志就不会有问题。如果一个阻塞在writeline处,那么另一个就会阻塞在lock外。等待阻塞线程释放锁后,进入该代码段。那么可以断定,日志文件也是有逻辑的。

代码如下

[csharp] 
view plain
 copy

 print
?

  1. class Program  
  2.     {  
  3.         public static StreamWriter sw = new StreamWriter(“C:\\threadlog.txt”true);  
  4.         private static string path = @“C:\Users\cjt.IT\Desktop\S20110908002112.info”;//大䨮文?件t内¨²容¨Y10M级?  
  5.         private Thread t1;  
  6.         private Thread t2;  
  7.   
  8.         static void Main(string[] args)  
  9.         {  
  10.             Program p=new Program();  
  11.             p.Test();  
  12.         }  
  13.   
  14.   
  15.         public void Test()  
  16.         {  
  17.             StreamReader sr = new StreamReader(path);  
  18.             string content = sr.ReadToEnd();  
  19.             BeginWrite1(content);  
  20.             BeginWrite2(content);  
  21.             sr.Close();  
  22.             //t1.Join();  
  23.             //sw.Close();  
  24.         }  
  25.   
  26.         private void BeginWrite1(string content)  
  27.         {  
  28.             t1 = new Thread(WriteLog);  
  29.             t1.Start(“———线?程¨¬1Begin——–“ + Environment.NewLine + content + “———线?程¨¬1 End——–“ + Environment.NewLine);  
  30.         }  
  31.   
  32.         private void BeginWrite2(string content)  
  33.         {  
  34.             t2 = new Thread(WriteLog);  
  35.             t2.Start(“———线?程¨¬2Begin——–“ + Environment.NewLine + content + “———线?程¨¬2End——–“ + Environment.NewLine);  
  36.         }  
  37.   
  38.         private void WriteLog(object content)  
  39.         {  
  40.             lock (path)//如果不加同步 由于 writeLine会发生阻塞.所´以当À另外一个?线?程¨¬也°2到Ì?WriteLine处ä|的Ì?时º¡À候¨°,ê?会¨¢发¤¡é生¦¨²  同ª?时º¡À写¡ä 异°¨¬常¡ê  
  41.             {  
  42.             sw.WriteLine(content.ToString());//内¨²容¨Y过y多¨¤,ê?线?程¨¬会¨¢阻Á¨¨塞¨?在¨²该?处ä|  
  43.             sw.Flush();//这a样¨´能¨¹够?保À¡ê证¡è 缓o冲?区?内¨²的Ì?数ºy据Y全¨?部?写¡ä入¨?磁ä?盘¨¬  
  44.             }  
  45.         }  
  46.   
  47.     }  


正常运行。查看日志结果


由StreamWriter.WriteLine 引发对C#多线程的深入思考(一)

可以看到可以认为是两个线程顺序执行了。没有出现混乱的情况。

(你可能已经注意到,上边的代码有两个问题,1 sw没有关闭 2 由于线程是前台线程,main结束后,该线程并没有结束,也就是说,我们不能单从窗口显示”结束”,已到达main结尾来断定写日志完成,我们需要等待一段时间,认为写日志完成了,再去查看日志)

但是,加锁后,这样的线程还有意义么

线程的目的是使得程序并发进行。也就是说,如果我们要想使得写日志加快,可以采用多线程写日志。但是,按照上边的情况来写日志,根本没有起到线程的优势。因为我们在一个线程阻塞的时候,并没有办法启动另一个线程!!而是等到这个阻塞完毕后,再调用另一个,这样和顺序执行就没有任何差别了。我们要的是及时唤醒另一个写进程(我们知道,这样带来的后果就是使得日志开始错乱,分不清是第一个写的还是第二个写的,甚至没有正常的语法!)

我们该怎么办?

好吧,这一篇已经够长了,大家估计已经没有耐性读下去了。。。

但是还有很多问题没解决,还有很多不清楚的地方,不是么?OK,在下一篇文章中,我们将继续对C#中的线程做进一步探究。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/126790.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • strstr函数用法小结

    strstr函数用法小结strstr函数原型:char*strstr(char*str1,char*str2);功能就是找出在字符串str1中第一次出项字符串str2的位置(也就是说字符串sr1中要包含有字符串str2),找到就返回该字符串位置的指针(也就是返回字符串str2在字符串str1中的地址的位置),找不到就返回空指针(就是null)。在C语言中strchr和strst

    2022年10月16日
  • 域名url转发怎么设置_url和域名

    域名url转发怎么设置_url和域名URL转发技术就是将该域名的网站访问请求,通过浏览器端技术,转向到另外一个网站。域名URL转发分为url显性转发和URL隐性转发这里以万网域名为例教你如何设置显性url转发和隐性URL转发。

    2022年10月18日
  • 小米MIX 解BL锁教程 申请BootLoader解锁教程

    小米MIX 解BL锁教程 申请BootLoader解锁教程小米MIX线刷兼救砖_解账户锁_纯净刷机包_教程一、准备工作1、注册小米账号:点击注册(已有小米账号请忽视)2、在手机中登陆【小米账号】3、下载并解压【小米解锁工具】或点击这里下载安装二、开始解锁1打开【小米解锁官网】:http://www.miui.com/unlock/,点击【立即解锁】,输入【小米账号】,点击【立即登录】,填写好上诉信息后,点击【立即申请】,输入【…

  • 爸妈老了

    爸妈老了爸妈老了

  • goland2022激活码 key is invalid【中文破解版】2022.02.23

    (goland2022激活码 key is invalid)最近有小伙伴私信我,问我这边有没有免费的intellijIdea的激活码,然后我将全栈君台教程分享给他了。激活成功之后他一直表示感谢,哈哈~IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.cn/100143.html…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号