C#多线程入门概念及技巧
- 一、什么是线程
- 1.1线程的概念
- 1.2为什么要多线程
- 1.3线程池
- 1.4线程安全
- 1.4.1同步机制
- 1.4.2原子操作
- 1.5线程安全示例
- 1.5.1示例一
- 1.5.2示例二
- 1.6C#一些自带的方法实现并行
- 1.6.1 Parallel——For、ForEach、Invoke
- 1.6.1 PLINQ——AsParallel、AsSequential、AsOrdered
- 1.7Semaphore
一、什么是线程
1.1线程的概念
- 线程是操作系统中能够独立运行的最小单位,也是程序中能够并发执行的一段指令序列
- 线程是进程的一部分,一个进程可以包括多个线程,这个线程可以共享进程的资源
- 进程有入口线程,也可用创建更多的线程
1.2为什么要多线程
- 批量重复任务希望同时进行
- 多个不同任务希望同时进行,并且互不干扰
1.3线程池
- 一组预先创建的线程,可以被重复使用来执行多个任务
- 避免频繁地创建和销毁线程,从而减少了现成创建和销毁的开销,提高了系统的性能和效率
- 异步编程默认使用线程池
1.4线程安全
多个线程访问共享资源时,对共享资源的访问不会导致数据不一致或不可预期的结果
1.4.1同步机制
- 用于协调和控制多个线程之间的执行顺序和互斥访问共享资源
- 确保线程按照特定的顺序执行,避免竞态条件和数据不一致的问题
1.4.2原子操作
- 在执行过程中不会被中断的操作,不可分割,要么完全执行,要么完全不执行,没有中间状态
- 在多线程环境下,原子操作能够保证数据的一致性和可靠性,避免出现竞太条件和数据竞争的问题
1.5线程安全示例
1.5.1示例一
两个线程对一个变量进行操作,每个线程都让count增加10000,代码如下:
namespace ThreadStudy
{class Thread_Lock{const int total = 100_000; public static int count = 0;static void Main(string[] args){Thread thread1 = new Thread(new ThreadStart(ThreadMethod));Thread thread2 = new Thread(new ThreadStart(ThreadMethod));thread1.Start();thread2.Start();thread1.Join();thread2.Join();Console.WriteLine($"Count:{count}");}public static void ThreadMethod(){for (int i = 0; i < total; i++)count++;}}
}
输出结果确不为两万,并且每次都不一样:
这是因为线程一在访问并修改这个变量值的时候,另一个线程也在访问并修改这个值,这就会导致一个线程修改后的值被另一个线程修改后的值给覆盖,这个时候我们就需要加锁,修改后的代码如下:
class Thread_Lock{const int total = 100_000; public static int count = 0;public static object lockobjcet = new object();static void Main(string[] args){Thread thread1 = new Thread(new ThreadStart(ThreadMethod));Thread thread2 = new Thread(new ThreadStart(ThreadMethod));thread1.Start();thread2.Start();thread1.Join();thread2.Join();Console.WriteLine($"Count:{count}");}public static void ThreadMethod(){for (int i = 0; i < total; i++){lock (lockobjcet)count++;//这么写也可用 原子操作://count++在底层可能经过了很多步才加一 这个过程中数据可能被其它线程更改//原子操作能一步完成,防止其它线程对变量进行更改//Interlocked.Increment(ref count);}}}
输出结果:
1.5.2示例二
正常结果是要输出0-19,不加锁的情况下就会输出一些无序数
public static Queue<int> queue = new Queue<int>();public static object lockObject = new object();static void Main(string[] args){Thread producer = new Thread(new ThreadStart(AddNumber));Thread consumer1 = new Thread(new ThreadStart(WriteNumber));Thread consumer2 = new Thread(new ThreadStart(WriteNumber));producer.Start();consumer1.Start();consumer2.Start();producer.Join();consumer1.Interrupt();consumer2.Interrupt();consumer1.Join();consumer2.Join();}public static void AddNumber(){for (int i = 0; i < 20; i++){Thread.Sleep(20);queue.Enqueue(i);}}public static void WriteNumber(){try{while (true){lock(lockObject)if (queue.TryDequeue(out var res)){Console.WriteLine(res);Thread.Sleep(1);}}}catch (Exception){Console.WriteLine("Thread interrupted");}}
输出结果:
1.6C#一些自带的方法实现并行
1.6.1 Parallel——For、ForEach、Invoke
正常For循环需要4s
class Program{static void Main(string[] args){var sw = Stopwatch.StartNew();for (int i = 0; i < 20; i++){Thread.Sleep(200);Console.WriteLine($"I:{i}");}Console.WriteLine($"Elapsed time: {sw.ElapsedMilliseconds}ms");}}
使用Parallel进行For循环:
效果提升近10倍,美滋滋
class Program{static void Main(string[] args){var sw = Stopwatch.StartNew();for (int i = 0; i < 20; i++){Thread.Sleep(200);Console.WriteLine($"I:{i}");}Console.WriteLine($"Elapsed time: {sw.ElapsedMilliseconds}ms");}}
1.6.1 PLINQ——AsParallel、AsSequential、AsOrdered
//ToDo 后续补充
1.7Semaphore
Semaphore可以控制线程开启的多少,比如Parallel.For开启了5个线程,而Semaphore定义只能开启三个,当有三个线程正在做时,那么其它的线程就不能够再做,Semaphore等待后要释放掉,最后面还需要Dispose,之前用Parallel在不控制线程的情况下需要400ms,现在控制线程数量,需要1400ms
static void Main(string[] args){var sw = Stopwatch.StartNew();//第一个参数 最开始有几个线程可以用 第二个参数 最多可以同时用几个线程var seamphore = new Semaphore(3, 3);Parallel.For(0, 20, i =>{seamphore.WaitOne();Thread.Sleep(200);Console.WriteLine($"I:{i}");seamphore.Release();});seamphore.Dispose();Console.WriteLine($"Elapsed time: {sw.ElapsedMilliseconds}ms");}