C#多线程学习笔记

时间:2021-12-10 05:43:27

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication19
{
class Program
{
//创建一个新线程
//static void Main(string[] args)
//{
// Thread t = new Thread(WriteY);//创建新线程
// t.Start();//启动新线程,执行writeY();
// for (int i = 0; i < 1000; i++) Console.Write("x");
// Console.ReadKey();
//}
//static void WriteY()
//{
// for (int i = 0; i < 1000; i++) Console.Write("y");
//}
/*
CLR 为每个线程分配各自独立的栈空间,因此局部变量是独立的。在下面的例子中,我们定义一个拥有局部变量的方法,
* 然后在主线程和新创建的线程中同时执行该方法。
*/
//static void Main(string[] args)
//{
// new Thread(Go).Start();
// Go();
// Console.ReadKey();
//}
//static void Go()
//{
// for (int i = 0; i < 5; i++)
// Console.Write('?');
//}
//线程可以通过对同一对象的引用来共享数据。
//由于两个线程是调用了同一个的ThreadTest实例上的Go(),它们共享了done字段
//,因此输出结果是一次 “ Done “,而不是两次。
//bool done;
//static void Main(string[] args)
//{
// Program tt = new Program();
// new Thread(tt.Go).Start();
// tt.Go();
// Console.ReadKey();
//}
//void Go()
//{
// if (!done) { done = true; Console.WriteLine("Done"); }
//}
//静态字段提供了另一种在线程间共享数据的方式,以下是一个静态的done字段的例子:
//static bool done;
//static void Main(string[] args)
//{
// new Thread(Go).Start();
// Go();
// Console.ReadKey();
//}
//static void Go()
//{
// if (!done) { done = true; Console.WriteLine("Done"); }
//}
/*
需要在读写公共字段时,获得一个排它锁(互斥锁,exclusive lock )。C# 提供了lock来达到这个目的:
* 当两个线程同时争夺一个锁的时候(例子中的locker),一个线程等待,或者说阻塞,直到锁变为可用。
* 这样就确保了在同一时刻只有一个线程能进入临界区(critical section,不允许并发执行的代码)
* ,所以 “ Done “ 只被打印了一次。像这种用来避免在多线程下的不确定性的方式被称为线程安全(thread-safe)。
*/
//static bool done;
//static readonly object locker = new object();
//static void Main(string[] args)
//{
// new Thread(Go).Start();
// Go();
// Console.ReadKey();
//}
//static void Go()
//{
// lock (locker)
// {
// if (!done) { Console.WriteLine("Done"); done = true; }
// }
//}
/*
可以通过调用线程的Join方法来等待另一个线程结束
当使用Sleep或Join等待时,线程是阻塞(blocked)状态,因此不会消耗 CPU 资源
Thread.Sleep(0)会立即释放当前的时间片,将 CPU 资源出让给其它线程。Framework 4.0 新的Thread.Yield()方法与其相同,除了它只会出让给运行在相同处理器核心上的其它线程。
Sleep(0)和Yield在调整代码性能时偶尔有用,它也是一个很好的诊断工具,
* 可以用于找出线程安全(thread safety)的问题。如果在你代码的任意位置插入Thread.Yield()会影响到程序
* ,基本可以确定存在 bug。
*/
//static void Main(string[] args)
//{
// Thread t = new Thread(Go);

// t.Start();
// t.Join();
// Thread.Sleep(TimeSpan.FromSeconds(1));
// Console.WriteLine("Thread t has ended");
// Console.ReadKey();
//}
//static void Go()
//{
// for (int i = 0; i < 1000; i++)
// Console.Write("y");
//}
/*使用Thread类的构造方法来创建线程,
通过传递ThreadStart委托来指明线程从哪里开始运行,
* 下面是ThreadStart委托的定义:
*/
//static void Go()
//{
// Console.WriteLine("hello!");
//}
//static void Main()
//{
// Thread t = new Thread( new ThreadStart(Go));
// t.Start();
// Go();
// Console.ReadKey();
//}
/*
另一个方法是向Thread的Start方法传递参数

*/
//static void Main()
//{
// Thread t = new Thread(Print);
// t.Start("Hello from t!");
// Console.ReadKey();
//}

//static void Print(object messageObj)
//{
// string message = (string)messageObj; // 需要强制类型转换
// Console.WriteLine(message);
//}
/*
每一个线程都有一个 Name 属性,我们可以设置它以便于调试
* 。这在 Visual Studio 中非常有用,因为线程的名字会显示在线程窗口(Threads Window)与调试位置(Debug Location)工具栏上。
* 线程的名字只能设置一次,以后尝试修改会抛出异常。
静态的Thread.CurrentThread属性会返回当前执行的线程。在下面的例子中,我们设置主线程的名字:
*/
//static void Main()
//{
// Thread.CurrentThread.Name = "main";
// Thread worker = new Thread(Go);
// worker.Name = "worker";
// worker.Start();
// Go();
// Console.ReadKey();
//}
//static void Go()
//{
// Console.WriteLine("hello from" + Thread.CurrentThread.Name);
//}
/*
默认情况下,显式创建的线程都是前台线程(foreground threads)。
* 只要有一个前台线程在运行,程序就可以保持存活,而后台线程(background threads)并不能保持程序存活。
* 当一个程序中所有前台线程停止运行时,
* 仍在运行的所有后台线程会被强制终止。
* 可以通过线程的IsBackground属性来查询或修改线程的前后台状态
在退出程序时可以显式的等待这些后台线程结束。有两种方法可以实现:
如果是自己创建的线程,在线程上调用Join方法。
如果是使用线程池线程,使用事件等待句柄。
* */
//static void Main(string[] args)
//{
// Thread worker = new Thread(() => Console.ReadLine());
// if (args.Length > 0) worker.IsBackground = true;
// worker.Start();
// Console.ReadKey();
//}
/*
如果是使用 .NET Framework 4.0 以前的版本,
* 则不能使用任务并行库。
* 你必须通过一种旧的构造使用线程池:ThreadPool.QueueUserWorkItem与异步委托。
* 这两者之间的不同在于异步委托可以让你从线程中返回数据,
* 同时异步委托还可以将异常封送回调用方。
*/
//static void main()
//{
// ThreadPool.QueueUserWorkItem(Go);
// ThreadPool.QueueUserWorkItem(Go, 123);
// Console.ReadKey();
//}
//static void Go(object data)
//{
// Console.WriteLine("hello form the thread pool!" + data);
//}
/*
要使用非泛型的Task类,调用Task.Factory.StartNew,并传递目标方法的委托:
*/
//static void Main()
//{
// Task.Factory.StartNew(Go);
// Console.ReadKey();
//}
//static void Go()
//{
// Console.WriteLine("Hello from thread pool!");
//}
//static void Main()
//{
// Func<string, int> method = Work;
// IAsyncResult cookie = method.BeginInvoke("test", null, null);
//
// 这里可以并行执行其它任务
//
//int result = method.EndInvoke(cookie);
//Console.WriteLine("String length is: " + result);
//Console.ReadKey();
//}

//static int Work(string s) { return s.Length; }
/*
下一步是同步(synchronization):为期望的结果协调线程的行为。当多个线程访问同一个数据时,同步尤其重要,但是这是一件非常容易搞砸的事情。

同步构造可以分为以下四类:

简单的阻塞方法
这些方法会使当前线程等待另一个线程结束或是自己等待一段时间。Sleep、Join与Task.Wait都是简单的阻塞方法。
锁构造
锁构造能够限制每次可以执行某些动作或是执行某段代码的线程数量。排它锁构造是最常见的,它每次只允许一个线程执行,从而可以使得参与竞争的线程在访问公共数据时不会彼此干扰。标准的排它锁构造是lock(Monitor.Enter/Monitor.Exit)、Mutex与 SpinLock。非排它锁构造是Semaphore、SemaphoreSlim以及读写锁。
信号构造
信号构造可以使一个线程暂停,直到接收到另一个线程的通知,避免了低效的轮询 。有两种经常使用的信号设施:事件等待句柄(event wait handle )和Monitor类的Wait / Pluse方法。Framework 4.0 加入了CountdownEvent与Barrier类。
非阻塞同步构造
非阻塞同步构造通过调用处理器指令来保护对公共字段的访问。CLR 与 C# 提供了下列非阻塞构造:Thread.MemoryBarrier 、Thread.VolatileRead、Thread.VolatileWrite、volatile关键字以及Interlocked类。
*/
}

//public delegate void ThreadStart();
}