大家好,又见面了,我是你们的朋友全栈君。
http://blog.csdn.net/nndtdx/article/details/6789810
首先,StreamWriter线程安全么?
答:StreamWriter 的构造以及StreamWriter.WriteLine(string)都是非线程安全的
我们封装两个写日志的方法。
底层都是由StreamWriter.writeline来实现.一个加锁,一个不加锁。将加锁的那个命名为safewritelog,另一个命名为unsafeWritelog.然后利用两个循环。不停的分别创建个线程,去写日志。测试看哪个会出现写异常。代码如下:
- namespace ThreadWriteLog
- {
- class Program
- {
- private static object ob = “哟内容!!”;
- static void Main(string[] args)
- {
- for (int i = 0; i < 10; i++)
- {
- Thread wrtieThread = new Thread(SafyWriteLog);
- wrtieThread.Name = “线程–“ + i;
- string content = “这是” + wrtieThread.Name + “的内容Y”;
- wrtieThread.Start(content);
- }
- for (int i = 0; i < 10; i++)
- {
- Thread wrtieThread = new Thread(UnSafyWriteLog);
- wrtieThread.Name = “线程¨¬–“ + i;
- string content = “这是” + wrtieThread.Name + “的内容Y”;
- wrtieThread.Start(content);
- }
- Console.WriteLine(“结束”);
- Console.Read();
- }
- public static void SafyWriteLog(object content)
- {
- string path = @“C:\SafeLog.txt”;
- lock (ob)
- {
- StreamWriter sw = File.AppendText(path);
- sw.WriteLine(content.ToString());
- sw.Close();
- }
- }
- public static void UnSafyWriteLog(object content)
- {
- string path = @“C:\UnSafeLog.txt”;
- StreamWriter sw = File.AppendText(path);
- sw.WriteLine(content.ToString());
- sw.Close();
- }
- }
- }
运行后,第一个for循环顺利结束,文件中显示 0-9进程没有问题。
这是线程–0的内容
这是线程–1的内容
这是线程–2的内容
这是线程–5的内容
这是线程–3的内容
这是线程–4的内容
这是线程–6的内容
这是线程–7的内容
这是线程–8的内容
这是线程–9的内容
也符合线程的概念,随着系统的随机调度而运行。
而第二个for循环没有正常完成,抛出异常
- 未处理的异常: 未处理的异常: 未处理的异常: 未处理的异常: 未处理的异常: 未处理的异
- 常: System.IO.IOException: 文件“C:\UnSafeLog.txt”正由另一进程使用,因此
- 该进程无法访问该文件。
- 在 System.IO.__Error.WinIOError(Int32 errorCode, StringmaybeFullPath)
- 在 System.IO.FileStream.Init(String path, FileMode mode,FileAccess access, I
- nt32rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions o
- ptions,SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy)
- 在 System.IO.FileStream..ctor(String path, FileMode mode,FileAccess access,
- FileShareshare, Int32 bufferSize, FileOptions options)
- 在 System.IO.StreamWriter.CreateFile(String path, Booleanappend)
- 在 System.IO.StreamWriter..ctor(String path, Booleanappend, Encoding encodin
- g, Int32bufferSize)
- 在 System.IO.StreamWriter..ctor(String path, Booleanappend)
- 在 System.IO.File.AppendText(Stringpath)
- 在 caTestProj.Program.UnSafyWriteLog(Object content) 位置 F:\ASP.NET\MyCode\c
- aTestProj\caTestProj\Program.cs:行号 51
- 在 System.Threading.ThreadHelper.ThreadStart_Context(Objectstate)
- 在 System.Threading.ExecutionContext.Run(ExecutionContextexecutionContext, C
- ontextCallbackcallback, Object state)
- 在 System.Threading.ThreadHelper.ThreadStart(Objectobj)System.IO.IOException
- : 文件“C:\UnSafeLog.txt”正由另一进程使用,因此该进程无法访问该文件。
- 在 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方法,出现异常,则说明非线程安全可没有异常,说明线程安全。
代码如下
- class Program
- {
- static object ob=new object();
- static string path = @“C:\UnSafeLog.txt”;
- private static StreamWriter sw2;
- static void Main(string[] args)
- {
- for (int i = 0; i < 10; i++)
- {
- Thread wrtieThread = new Thread(SafyWriteLog);
- wrtieThread.Name = “线程–“ + i;
- string content = “这是” + wrtieThread.Name + “的内容”;
- wrtieThread.Start(content);
- }
- sw2 = File.AppendText(path);
- for (int i = 0; i < 10; i++)
- {
- Thread wrtieThread = new Thread(UnSafyWriteLog);
- wrtieThread.Name = “线程–“ + i;
- string content = “这是” + wrtieThread.Name + “的内容Y”;
- wrtieThread.Start(content);
- }
- sw2.Close();
- Console.WriteLine(“结束”);
- Console.Read();
- }
- public static void SafyWriteLog(object content)
- {
- string path = @“C:\SafeLog.txt”;
- lock (ob)
- {
- StreamWriter sw = File.AppendText(path);
- sw.WriteLine(content.ToString());
- sw.Close();
- }
- }
- public static void UnSafyWriteLog(object content)
- {
- sw2.WriteLine(content.ToString());
- }
- }
运行后,貌似没出现什么问题。两个for循环都正常执行完毕了。
而且日志文件中的记录也是按顺序来的,没有出现日志文字错乱现象。
这是线程–0的内容
这是线程–1的内容
这是线程–2的内容
这是线程–3的内容
这是线程–4的内容
这是线程–5的内容
这是线程–6的内容
这是线程–7的内容
这是线程–8的内容
这是线程–9的内容
但真的是这样的么?构造是非线程安全的,而writeline是线程安全的??不可能的。
仔细考虑,只有在某一个线程阻塞在WriteLine的时候,另一个线程也访问该方法的时候,才会出现多线程写的情况。
那么,ok,我们加大一次文字的写入量,使其阻塞在WriteLine这里。
现在,我们构造更加简单的场景,创建两个线程,一次写入大量日志,使用同一个StreamWriter对象。如果writeline方法是非线程安全的,那么肯定会出现异常。
代码如下:
- class Program
- {
- public static StreamWriter sw = new StreamWriter(“C:\\threadlog.txt”, true);
- private static string path = @“C:\Users\cjt.IT\Desktop\20110807\S20110807031902.info”;//大文件内容10M级
- private Thread t1;
- private Thread t2;
- static void Main(string[] args)
- {
- Program p=new Program();
- p.Test();
- }
- public void Test()
- {
- StreamReader sr = new StreamReader(path);
- string content = sr.ReadToEnd();
- BeginWrite1(content);
- BeginWrite2(content);
- sr.Close();
- //t1.Join();
- //sw.Close();
- }
- private void BeginWrite1(string content)
- {
- t1 = new Thread(WriteLog);
- t1.Start(“———线程1Begin——–“ + Environment.NewLine + content + “———线程1 End——–“ + Environment.NewLine);
- }
- private void BeginWrite2(string content)
- {
- t2 = new Thread(WriteLog);
- t2.Start(“———线程2Begin——–“ + Environment.NewLine + content + “———线程2End——–“ + Environment.NewLine);
- }
- private void WriteLog(object content)
- {
- sw.WriteLine(content.ToString());//内容过多线程会阻塞在该处
- sw.Flush();//这a样能够保证缓o冲区内的数据全部写磁盘¨¬
- }
- }
运行之后,立即出现异常。
- 未处理的异常: System.IndexOutOfRangeException: 在复制内存时检测到可能的 I/O 争
- 用条件。默认情况下,I/O 包不是线程安全的。在多线程应用程序中,必须以线程安全方式
- (如 TextReader 或 TextWriter 的 Synchronized 方法返回的线程安全包装)访问流。这也
- 适用于 StreamWriter 和 StreamReader 这样的类。
- 在 System.Buffer.InternalBlockCopy(Array src, Int32 srcOffset, Array dst, Int
- 32 dstOffset, Int32 count)
- 在 System.IO.StreamWriter.Write(Char[] buffer, Int32 index, Int32 count)
- 在 System.IO.TextWriter.WriteLine(String value)
- 在 caTestProj.Program.WriteLog(Object content) 位置 F:\ASP.NET\MyCode\caTestP
- roj\caTestProj\Program.cs:行号 51
- 在 System.Threading.ThreadHelper.ThreadStart_Context(Object state)
- 在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, C
- ontextCallback callback, Object state)
- 在 System.Threading.ThreadHelper.ThreadStart(Object obj)
- 请按任意键继续. . .
从堆栈跟踪来看:
在调用TextWriter.WriteLine(Streamwriter继承于此)时出错,系统要进行内存拷贝,出现I/O争用。也就是说,线程不安全本质是由(至少该例子中是由I/o争用导致的)。两者都要将自己的内容拷贝到磁盘上,显然要出错。writeline方法缺少同步机制,抛出异常。
这里是日志内容一部分
可以看出来中间有个线程2begin,可以肯定,在这里,线程1暂时阻塞,然后线程2开始写,也就是说从这里开始,日志文件就开始混乱了,因为1已经阻塞了,这里没有出现异常。
日志文件结束
中间并没有出现线程2end的标记,说明,线程2中间也阻塞了。这是启动了线程1,造成中间这个界限丢失了。也有可能从线程2begin这一段,线程1和线程2就是混着写的。直到他们两个突然决定同时写,系统出现I/O争用错误,抛出异常。
如果我对其加锁呢?
显然我们如果控制了同时writelien的只有一个线程。那么写日志就不会有问题。如果一个阻塞在writeline处,那么另一个就会阻塞在lock外。等待阻塞线程释放锁后,进入该代码段。那么可以断定,日志文件也是有逻辑的。
代码如下
- class Program
- {
- public static StreamWriter sw = new StreamWriter(“C:\\threadlog.txt”, true);
- private static string path = @“C:\Users\cjt.IT\Desktop\S20110908002112.info”;//大䨮文?件t内¨²容¨Y10M级?
- private Thread t1;
- private Thread t2;
- static void Main(string[] args)
- {
- Program p=new Program();
- p.Test();
- }
- public void Test()
- {
- StreamReader sr = new StreamReader(path);
- string content = sr.ReadToEnd();
- BeginWrite1(content);
- BeginWrite2(content);
- sr.Close();
- //t1.Join();
- //sw.Close();
- }
- private void BeginWrite1(string content)
- {
- t1 = new Thread(WriteLog);
- t1.Start(“———线?程¨¬1Begin——–“ + Environment.NewLine + content + “———线?程¨¬1 End——–“ + Environment.NewLine);
- }
- private void BeginWrite2(string content)
- {
- t2 = new Thread(WriteLog);
- t2.Start(“———线?程¨¬2Begin——–“ + Environment.NewLine + content + “———线?程¨¬2End——–“ + Environment.NewLine);
- }
- private void WriteLog(object content)
- {
- lock (path)//如果不加同步 由于 writeLine会发生阻塞.所´以当À另外一个?线?程¨¬也°2到Ì?WriteLine处ä|的Ì?时º¡À候¨°,ê?会¨¢发¤¡é生¦¨² 同ª?时º¡À写¡ä 异°¨¬常¡ê
- {
- sw.WriteLine(content.ToString());//内¨²容¨Y过y多¨¤,ê?线?程¨¬会¨¢阻Á¨¨塞¨?在¨²该?处ä|
- sw.Flush();//这a样¨´能¨¹够?保À¡ê证¡è 缓o冲?区?内¨²的Ì?数ºy据Y全¨?部?写¡ä入¨?磁ä?盘¨¬
- }
- }
- }
正常运行。查看日志结果
可以看到可以认为是两个线程顺序执行了。没有出现混乱的情况。
(你可能已经注意到,上边的代码有两个问题,1 sw没有关闭 2 由于线程是前台线程,main结束后,该线程并没有结束,也就是说,我们不能单从窗口显示”结束”,已到达main结尾来断定写日志完成,我们需要等待一段时间,认为写日志完成了,再去查看日志)
但是,加锁后,这样的线程还有意义么
线程的目的是使得程序并发进行。也就是说,如果我们要想使得写日志加快,可以采用多线程写日志。但是,按照上边的情况来写日志,根本没有起到线程的优势。因为我们在一个线程阻塞的时候,并没有办法启动另一个线程!!而是等到这个阻塞完毕后,再调用另一个,这样和顺序执行就没有任何差别了。我们要的是及时唤醒另一个写进程(我们知道,这样带来的后果就是使得日志开始错乱,分不清是第一个写的还是第二个写的,甚至没有正常的语法!)
我们该怎么办?
好吧,这一篇已经够长了,大家估计已经没有耐性读下去了。。。
但是还有很多问题没解决,还有很多不清楚的地方,不是么?OK,在下一篇文章中,我们将继续对C#中的线程做进一步探究。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/126790.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...