[C#多线程编程(一)] 导读
原文:http://www.albahari.com/threading/
PART 1: GETTING STARTED
概念与简介
C# supports parallel execution of code through multithreading. A thread is an independent execution path, able to run simultaneously with other threads.
C#通过多线程来支持代码的并行执行。一个线程是一个独立的执行路径,可以与其它线程同时运行。
A C# client program (Console, WPF, or Windows Forms) starts in a single thread created automatically by the CLR and operating system (the “main” thread), and is made multithreaded by creating additional threads. Here’s a simple example and its output:
C#客户端程序(控制台,WPF或者Windows窗体程序)以一个单线程启动,这个线程由CLR和操作系统自动创建("主“线程),而后通过创建额外的线程来实现多线程。这里有个简单的例子以及它的输出结果:
Notice:All examples assume the following namespaces are imported:
全部示例都假定你已经导入了如下的命名空间:
using System; using System.Threading;
1 class ThreadTest 2 { 3 static void Main() 4 { 5 Thread t = new Thread (WriteY); // Kick off a new thread 6 t.Start(); // running WriteY() 7 8 // Simultaneously, do something on the main thread. 9 for (int i = 0; i < 1000; i++) Console.Write ("x"); 10 } 11 12 static void WriteY() 13 { 14 for (int i = 0; i < 1000; i++) Console.Write ("y"); 15 } 16 }
xxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyy
yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
yyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
...
The main thread creates a new thread t
on which it runs a method that repeatedly prints the character “y”. Simultaneously, the main thread repeatedly prints the character “x”:
主线程创建了一个新的线程t,通过这个线程运行的一个不断打印出字符y的一个方法。同时主线程里不断的重复打印字符x:
Once started, a thread’s IsAlive
property returns true
, until the point where the thread ends. A thread ends when the delegate passed to the Thread
’s constructor finishes executing. Once ended, a thread cannot restart.
一旦启动,线程的IsAlive属性会返回true,直到该线程结束为止。当传给该线程构造方法的代理执行完成,线程也就结束了。一旦线程结束就不能重新启动了。
The CLR assigns each thread its own memory stack so that local variables are kept separate. In the next example, we define a method with a local variable, then call the method simultaneously on the main thread and a newly created thread:
CLR分给每个线程它们自己的内存栈以便本地变量保持隔离。在下一个例子中,我们定义一个带有本地变量的方法,然后同时在主线程与新建线程里调用该方法:
1 static void Main() 2 { 3 new Thread (Go).Start(); // Call Go() on a new thread 4 Go(); // Call Go() on the main thread 5 } 6 7 static void Go() 8 { 9 // Declare and use a local variable - 'cycles' 10 for (int cycles = 0; cycles < 5; cycles++) Console.Write ('?'); 11 }
??????????
A separate copy of the cycles variable is created on each thread's memory stack, and so the output is, predictably, ten question marks.
在每个线程的内存栈上创建了cycles编程的独自的拷贝,所以输出结果也能料到会是10个问号。
Threads share data if they have a common reference to the same object instance. For example:
如果线程共同引用同一个对象实例可以共享数据。例如:
1 class ThreadTest 2 { 3 bool done; 4 5 static void Main() 6 { 7 ThreadTest tt = new ThreadTest(); // Create a common instance 8 new Thread (tt.Go).Start(); 9 tt.Go(); 10 } 11 12 // Note that Go is now an instance method 13 void Go() 14 { 15 if (!done) { done = true; Console.WriteLine ("Done"); } 16 } 17 }
Because both threads call Go()
on the same ThreadTest
instance, they share the done
field. This results in "Done" being printed once instead of twice:
由于两个线程都调用了同一个ThreadTest实例的Go()方法,所以它们共享done字段。这也导致了"Done"被打印了一次而不是两次:
Done
Static fields offer another way to share data between threads. Here’s the same example with done
as a static field:
静态字段提供了另一种线程间分享数据的方式。这里是同一例子done被设为了static静态字段:
1 class ThreadTest 2 { 3 static bool done; // Static fields are shared between all threads 4 5 static void Main() 6 { 7 new Thread (Go).Start(); 8 Go(); 9 } 10 11 static void Go() 12 { 13 if (!done) { done = true; Console.WriteLine ("Done"); } 14 } 15 }
Both of these examples illustrate another key concept: that of thread safety (or rather, lack of it!) The output is actually indeterminate: it’s possible (though unlikely) that “Done” could be printed twice. If, however, we swap the order of statements in the Go
method, the odds of “Done” being printed twice go up dramatically:
这2个例子都演示了另一个关键的概念:那就是”线程安全“()。输出结果实际是不确定的:可能"Done"会被打印两次(虽然不太可能)。但是,如果我们交换Go方法里面的语句的顺序,”Done"被打印两次的几率就会显著地上升。
1 static void Go() 2 { 3 if (!done) { Console.WriteLine ("Done"); done = true; } 4 }
Done
Done (usually!)
The problem is that one thread can be evaluating the if
statement right as the other thread is executing theWriteLine
statement — before it’s had a chance to set done
to true.
这个问题是一个线程正在计算if语句而恰恰另一个线程正在执行WriteLine语句----这正发生在它有机会把done设成true之前。
The remedy is to obtain an exclusive lock while reading and writing to the common field. C# provides the lockstatement for just this purpose:
补救方式是在读写时获取一个公共字段的“排斥锁”。C#提供的lock语句正是为了这个目的:
1 class ThreadSafe 2 { 3 static bool done; 4 static readonly object locker = new object(); 5 6 static void Main() 7 { 8 new Thread (Go).Start(); 9 Go(); 10 } 11 12 static void Go() 13 { 14 lock (locker) 15 { 16 if (!done) { Console.WriteLine ("Done"); done = true; } 17 } 18 } 19 }
When two threads simultaneously contend a lock (in this case, locker
), one thread waits, or blocks, until the lock becomes available. In this case, it ensures only one thread can enter the critical section of code at a time, and “Done” will be printed just once. Code that's protected in such a manner — from indeterminacy in a multithreading context — is called thread-safe.
当两个线程同时竞争一个锁时(本例中是locker),一个线程等待或者阻塞,直到该锁可用为止。在这个例子中,它保证同时只能有一个线程进入临界区,而"Done"将会只打印一次。代码以这样一种方式被保护被称作“线程安全的”(免除在一个多线程上下文里的不确定性)。
注:Shared data is the primary cause of complexity and obscure errors in multithreading. Although often essential, it pays to keep it as simple as possible.
A thread, while blocked, doesn't consume CPU resources.。
共享的数据是造成多线程中的复杂与不确定错误的主要原因。 虽然常常必要但还是要花些心思保持尽可能简单.
Join and Sleep
You can wait for another thread to end by calling its Join
method. For example:
你可以通过调用Join方法等待另一个线程结束。例如:
1 static void Main() 2 { 3 Thread t = new Thread (Go); 4 t.Start(); 5 t.Join(); 6 Console.WriteLine ("Thread t has ended!"); 7 } 8 9 static void Go() 10 { 11 for (int i = 0; i < 1000; i++) Console.Write ("y"); 12 }
This prints “y” 1,000 times, followed by “Thread t has ended!” immediately afterward. You can include a timeout when calling Join
, either in milliseconds or as a TimeSpan
. It then returns true
if the thread ended or false
if it timed out.
这个打印1000次y字符,之后在跟着输出" Thread t has ended!".当调用Join时你可以加一个超时时间,或者用毫秒数或者用一个TimeSpan.如果该线程结束了它会返回true或者超时时返回false.
Thread.Sleep
pauses the current thread for a specified period:
Thread.Sleep会暂停(挂起?)当前线程一个特定时间段:
1 Thread.Sleep (TimeSpan.FromHours (1)); // sleep for 1 hour 2 Thread.Sleep (500); // sleep for 500 milliseconds
While waiting on a Sleep
or Join
, a thread is blocked and so does not consume CPU resources.
当以Sleep或者Join等待时,线程会被阻塞所以不会消耗CPU资源。
Thread.Sleep(0) relinquishes the thread’s current time slice immediately, voluntarily handing over the CPU to other threads. Framework 4.0’s new Thread.Yield() method does the same thing — except that it relinquishes only to threads running on the same processor.
Thread.Sleep(0)会立即放弃该线程的当前时间片,自愿移交CPU给其它的线程。.NET框架4.0的新的Thread.Yield()方法做同样的事---只不过它只放弃给同一个处理器上运行的线程。
Sleep(0) or Yield is occasionally useful in production code for advanced performance tweaks. It’s also an excellent diagnostic tool for helping to uncover thread safety issues: if inserting Thread.Yield() anywhere in your code makes or breaks the program, you almost certainly have a bug.
Sleep(0)或者Yield偶尔会用在生产代码中为了更高一点儿的性能。它也是一个优秀的诊断工具用来辅助发现线程安全问题:如果在你代码任何地方插入Thread.Yield()都能中断程序,一定有bug.
(续)导读2>>