小弟接触线程不多,今天试着写个程序,三个线程,每个线程分别打印A,B,C三个字母 7次,最后执行结果为:ABCABCABC........
代码:
1 class Program 2 { 3 int count = 1; 4 AutoResetEvent areA = new AutoResetEvent(false); 5 AutoResetEvent areB = new AutoResetEvent(false); 6 AutoResetEvent areC = new AutoResetEvent(false); 7 object lockme = new object(); 8 9 static void Main(string[] args) 10 { 11 Program program = new Program(); 12 Thread A = new Thread(program.PrintA); 13 Thread B = new Thread(program.PrintB); 14 Thread C = new Thread(program.PrintC); 15 16 A.Name = "A"; 17 B.Name = "B"; 18 C.Name = "C"; 19 20 A.Start(); 21 Thread.Sleep(100); 22 B.Start(); 23 Thread.Sleep(100); 24 C.Start(); 25 //D.Start(); 26 Console.WriteLine("主线程执行结束"); 27 Console.ReadKey(); 28 } 29 30 private void PrintC() 31 { 32 for (int i = 0; i <= 19; i++) 33 { 34 Thread.Sleep(100); 35 //lock (lockme) 36 //{ 37 if (count % 3 == 0) 38 { 39 Console.WriteLine("C"); 40 count++; 41 areC.Set(); 42 } 43 else if (count % 3 == 1) 44 { 45 areA.WaitOne(); 46 } 47 //} 48 } 49 } 50 51 private void PrintB() 52 { 53 for (int i = 0; i <= 19; i++) 54 { 55 Thread.Sleep(100); 56 //lock (lockme) 57 //{ 58 if (count % 3 == 2)/*当前线程可打印*/ 59 { 60 Console.WriteLine("B"); 61 count++; 62 areB.Set(); 63 } 64 else if (count % 3 == 0) 65 { 66 areC.WaitOne(); 67 } 68 //} 69 } 70 } 71 72 private void PrintA() 73 { 74 for (int i = 0; i <= 19; i++) 75 { 76 Thread.Sleep(100); 77 //lock (lockme) 78 //{ 79 if (count % 3 == 1) /*当前线程可打印*/ 80 { 81 Console.WriteLine("A"); 82 count++; 83 areA.Set(); 84 } 85 else if (count % 3 == 2) 86 { 87 areB.WaitOne(); 88 } 89 //} 90 } 91 } 92 }
问题1:加上lock锁后(代码中注释掉部分),执行结果如下:
三个线程对count进行读写操作,这里加上lock应该是合理的,为什么执行结果不正确呢?
问题2:去除程序中34,55,76行中的Thread.Sleep(100);程序结果如下:
为什么会加上Thread.Sleep(100);这一句呢?因为在调试的时候在32,53,74行加入断点,单步执行后程序正常输出ABCABC...,直接执行就是以上结果。这是为什么呢?
因为线程执行的时候是任务抢占式执行,谁抢到当前时间片谁就执行,所以就不会输出正确代码了,但是你加上sleep以后,导致当前线程因为挂起不再抢占了……所以你 歪打正着输出了正确答案,但是你的思路有问题,我把正确代码给你贴出来如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Threading; namespace ConsoleApplication9 { class Program { public static AutoResetEvent flag1 = new AutoResetEvent(false); public static AutoResetEvent flag2 = new AutoResetEvent(false); public static AutoResetEvent flag3 = new AutoResetEvent(false); static void Main(string[] args) { Thread th1 = new Thread(new ThreadStart(A)); Thread th2 = new Thread(new ThreadStart(B)); Thread th3 = new Thread(new ThreadStart(C)); th1.Start(); th2.Start(); th3.Start(); } static void A() { flag1.Set(); for (int i = 0; i < 7; i++) { flag1.WaitOne(); Console.WriteLine("A"); flag2.Set(); } } static void B() { for (int i = 0; i < 7; i++) { flag2.WaitOne(); Console.WriteLine("B"); flag3.Set(); } } static void C() { for (int i = 0; i < 7; i++) { flag3.WaitOne(); Console.WriteLine("C"); flag1.Set(); } } } }
思路确实有问题,每个线程多次执行WaitOne,很容易死锁。程序执行混乱,可读性不好。通过您的答案,提高了小弟对同步事件的理解,谢谢!
你这不是在写代码,你这是在编绕口令。
最近研究多线程,这些是测试代码,对于问题1、2,请教大神帮忙解惑。
@三当家: 我们先说问题1,既然你在问“为什么执行结果不正确”,那么请告知“正确的执行结果”是什么?
@Launcher: 正确的输出:ABCABCABCABC....
@三当家: 根据你写的代码,请讲一下为什么正确的输出是“ABCABCABCABC”?
@三当家: 三个线程分别打印abc,你怎么想输出abcabcabc...这是什么需求?单核cpu可以满你,1楼的答案也不能说一定满足的。
用
Timer(TimerCallback callback, object state, int dueTime, int period);
ThreadPool.QueueUserWorkItem()
具体用法自己在百度一下
我认为本质是楼没有理解到线程的用法,我很少用AutoResetEvent这种,这个应该就是平时说的pv操作吧。你打印里面的count%就是有问题的,count是多个线程的临界资源,只要处理lock count的地方就可以了。你开多个线程不是为了一起处理吗?还是这样一个p,一个v,有什么意思。
代码思路确实有问题,lock count只能保证临界资源的安全,但不能协调多个线程之间的执行。比如ab两个线程,当a线程的任务执行了75%的时候,需要等待b线程的任务执行完毕才能执行,此时就需要同步事件来协调两个线程之间的执行。
"三个线程分别打印abc,你怎么想输出abcabcabc...这是什么需求?单核cpu可以满你,1楼的答案也不能说一定满足的。"
这个需求只是用来熟悉多线程,听说(也只是听说)是迅雷早前的面试题。其实也可以应用到很多场景,比如流水线上的多个工序,可以同时运行,期间可能需要某个工序停止等待其它工序完成.......最后按照业务规则组装为成品。
多线程的启动顺序确实不能保证,但可以控制多线程的执行。通常并不是在一个线程开始时就阻塞其运行,这和把多个任务放到一个线程里执行的效果一样,属于在工作线程内串行执行。而1楼的答案协调了线程的执行,多核cpu下仍然是稳定的。
@三当家: 流水线,流水线都是一条对应一条线上,不会这样设计的。可以三条三并行,但是不会有关系,不然就不叫流水线了。然后你说的稳定只是表面的稳定,也可以说是稳定吧,但是从思想上来说完全是以你这几个线程的先后顺序来决定的,根本不是真正的控制。
@gw2010: 线程的先后顺序?是指执行顺序还是启动顺序?启动顺序确实控制不了,但是执行是可以控制的。