多线程入门

参考资料

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 另一个方法是向ThreadStart方法传递参数:

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资源。
posted @ 2020-12-08 09:58  海龟123  阅读(85)  评论(0编辑  收藏  举报