多线程入门
参考资料
1. C#多线程基础知识
https://blog.csdn.net/All_Rights_Reserved/article/details/119248622
http://www.albahari.com/threading/
github 实例:Multithreading-master
1.创建与传参
1.1 创建方法:
1.1.1 使用委托创建
public delegate void ThreadStart(); class ThreadTest { static void Main() { Thread t = new Thread (new ThreadStart (Go)); t.Start(); // 在新线程运行 GO() Go(); // 同时在主线程运行 GO() } static void Go() { Console.WriteLine ("hello!"); } }
1.1.2.使用方法组
线程也可以使用更简洁的语法创建,使用方法组(method group),让 C# 编译器推断ThreadStart
委托类型:ThreadStart 或者ParameterizedThreadStart
Thread t = new Thread (Go); // 无需显式使用 ThreadStart
1.1.3. 匿名方法或 lambda 表达式 (有参数时候推荐)
static void Main() { Thread t = new Thread ( () => Console.WriteLine ("Hello!") ); t.Start(); }
ps:
在2.0之前的C#版本中,声明委托的唯一方法是使用命名方法。C# 2.0引入了匿名方法,而在C# 3.0及更高版本中,Lambda表达式取代了匿名方法,作为编写内联代码的首先方式。
匿名方法与lambda
2. 向线程传递参数
2.1.lambda 表达式
向一个线程的目标方法传递参数最简单的方式是使用 lambda 表达式调用目标方法,在表达式内指定参数:
static void Main() { Thread t = new Thread ( () => Print ("Hello from t!") ); t.Start(); } static void Print (string message) { Console.WriteLine (message); }
2.2 另一个方法是向Thread
的Start
方法传递参数:
static void Main() { Thread t = new Thread (Print); t.Start ("Hello from t!"); } static void Print (object messageObj) { string message = (string) messageObj; // 需要强制类型转换 Console.WriteLine (message); }
可以这样是因为Thread
的构造方法通过重载来接受两个委托中的任意一个:
public delegate void ThreadStart(); public delegate void ParameterizedThreadStart (object obj);
ParameterizedThreadStart
的限制是它只接受一个参数。并且由于它是object
类型,通常需要类型转换。
2.线程状态 及前后台进程
2.1 线程状态
1. 暂停线程
Thread.Sleep(1000);
2.等待线程
thread.Join();
这两个的状态都是 WaitSleepJoin
3.终止线程
thread.Abort();
这个状态是 AbortRequested ==》 Stopped
4.检查线程状态
t1.ThreadState
[ComVisible(true)]
[Flags]
public enum ThreadState
{
Running = 0, //2. thread.start()
StopRequested = 1,
SuspendRequested = 2,
Background = 4,
Unstarted = 8, // 1. Thread thread = new Thread(Common.PrintNumbersWithStatus);
Stopped = 16, //4. 当线程指向的方法执行完时候
WaitSleepJoin = 32, //3. Thread.Sleep or thread.join()
Suspended = 64,
AbortRequested = 128, // 4.thread.Abort();
Aborted = 256 //5. thread.Abort(); }
2.2 前台与后台线程
默认情况下,显式创建的线程都是前台线程(foreground threads)。只要有一个前台线程在运行,程序就可以保持存活,而后台线程(background threads)并不能保持程序存活。当一个程序中所有前台线程停止运行时,仍在运行的所有后台线程会被强制终止。
后台线程可以使用 join 来等待结束
2.3 线程优先级
只有当多个线程同时处于活动状态时,这才有意义
enum ThreadPriority { Lowest, BelowNormal, Normal, AboveNormal, Highest }
在提升线程的优先级之前仔细考虑——它可能导致其他线程资源匮乏等问题。
2.4 异常处理
public static void Main() { try { new Thread (Go).Start(); } catch (Exception ex) { // We'll never get here! Console.WriteLine ("Exception!"); } } static void Go() { throw null; }
以上主线程捕获不到Go 方法中(子线程)抛出的异常,要知道每个线程都是一个独立的路线
3.共享数据与锁
3.1 字段共享
3.1 .1 私有字段共享
class ThreadTest { bool done; static void Main() { ThreadTest tt = new ThreadTest(); // Create a common instance new Thread (tt.Go).Start(); tt.Go(); } // Note that Go is now an instance method void Go() { if (!done) { done = true; Console.WriteLine ("Done"); } } }
因为两个线程都在同一个 ThreadTest 实例上调用 Go(),所以它们共享 done 字段。这导致“完成”被打印一次而不是两次:
3.1.2 静态字段共享
静态字段提供了另一种在线程之间共享数据的方法。这是作为静态字段完成的相同示例:
class ThreadTest { static bool done; // Static fields are shared between all threads static void Main() { new Thread (Go).Start(); Go(); } static void Go() { if (!done) { done = true; Console.WriteLine ("Done"); } } }
3.1.3 线程安全(线程安全指的就是一次只能有一个线程访问)
这两个例子都说明了另一个关键概念:线程安全(或者更确切地说,缺乏它!)输出实际上是不确定的:有可能(虽然不太可能)“完成”可以打印两次。然而,如果我们交换 Go 方法中语句的顺序,“Done”被打印两次的几率会急剧上升:
static void Go() { if (!done) { Console.WriteLine ("Done"); done = true; } }
原因是:一个线程在评估if语句的时候,另一个线程正在执行WriteLine语句--第一个线程就有可能评估通过。
补救方法是在对公共字段进行读写时获取排他锁。 C#为此目的提供了lock语句:
class ThreadSafe { static bool done; static readonly object locker = new object(); static void Main() { new Thread (Go).Start(); Go(); } static void Go() { lock (locker) { if (!done) { Console.WriteLine ("Done"); done = true; } } } }
当两个线程同时争用一个锁(在本例中为 locker)时,一个线程会等待或阻塞,直到锁可用。在这种情况下,它确保一次只有一个线程可以进入代码的临界区,并且“Done”只会打印一次。以这种方式保护的代码(在多线程上下文中不受不确定性影响)被称为线程安全的。
3.2 总结
- 共享数据是多线程中复杂和模糊错误的主要原因。虽然通常是必不可少的,但保持尽可能简单是值得的。
- 一个线程在被阻塞时,并不消耗CPU资源。