多线程(V1.0)

多线程基础知识

  • 什么是进程
  • 什么是线程
  • 什么是多线程
  • 什么是Thread
  • CurrentThread
  • ManagedThreadId
  • 异步多线程
  • 同步单线程与异步多线程的区别
  • 多线程进行顺序控制
  • 各版本多线程比对
    • 1. .NetFrameWork 1.0 1.1
    • 2..NetFrameWork 2.0(推出新的CLR)
    • 3..NetFrameWork 3.0 Task
  • 多线程安全问题
    • 线程安全问题解决
  • 锁的使用和锁关键字this
    • 锁this变量
  • await 、async
      • 语法

什么是进程

程序在服务器上运行,占据的计算资源集合,称之为进程。
进程之间不会相互干扰。
进程间通信比较困难

什么是线程

程序执行的最小单位,也有自己的计算资源
线程是属于进程,一个进程可以有多个线程

什么是多线程

一个进程里面,有多个线程并发执行

什么是Thread

Thread是一个封装类,是NetFramework对线程对象的抽象封装
通过Thread去完成的操作,最终是通过向操作系统请求得到的执行流

CurrentThread

CurrentThread表示当前线程

ManagedThreadId

ManagedThreadId是Net平台给Thread起的名字,一般是Int值,尽量不重复

任何异步多线程都离不开委托delegate
lambda
action
func

异步多线程

异步多线程发起调用,不等待结束,直接执行下一行代码。动作由新线程(子线程)来执行

同步单线程与异步多线程的区别

1.同步单线程界面卡顿,异步多线程界面不卡顿;
因为同步单线程的时候,主线程忙于处理逻辑计算,不能响应;异步多线程则将计算任务交给子线程,主线程已空闲,可以响应界面操作。
2.同步单线程处理事件较长,异步多线程处理快速;
同步单线程因为只有一个线程处理,异步多线程有多个线程并发计算。多线程是利用资源换取性能,短时间那些占用更多资源。
3.多线程的协调管理成本是存在的,多线程的效率不一定是线性增加的;
4.资源也一样上限,多线程就像有多辆车在路上行驶,但是车道的数量是有限;
5.线程也不是越多越好,管理成本会上升
6.多线程执行的无序性。启动无序(需要CPU分片处理),结束无序,执行时间不确定,同一线程同一任务耗时也不相同,这和操作系统的调度策略有关(CPU分片),线程优先级可以印象操作系统的调度

使用多线程不要通过延时去掌控顺序

多线程进行顺序控制

1.异步回调
2.IsComplete属性。用于判断当前异步操作是否完成
3. 利用信号量控制。WaitOne完成异步等待
阻塞当前线程,直到收到信号量,从AsyncResult发出,无延迟
waitOne(-1);//表示一直等待
waitOne(100);//最多等100ms,超时不再等待
4.EndInvoke获取异步结果返回值
如果想获取异步调用的真是返回值,只能使用Func委托中的EndInvoke

各版本多线程比对

1. .NetFrameWork 1.0 1.1

ThreadStart start = ()=>
{
	//逻辑处理
};
 Thread thread = new Thread(start);
 thread.Start();

优点
1.Thread的API丰富,如挂起(Supend),恢复(Resume),等待(Join),后台线程,销毁(Abort),重置销毁(ResetAbort)

缺点
1.线程资源是操作系统管理的,响应并不灵敏,所以不好控制
2.Thread启动线程是没有限制的,可能导致死机

2…NetFrameWork 2.0(推出新的CLR)

ThreadPool—池化资源管理,就是做个容器,容器提前申请线程,程序需要使用线时,直接找容器获取,用往后再放回容器(控制状态),避免频繁的申请和销毁,容器会根据线程的数量去申请和释放

WaitCallback callback = o=>
{
	//执行逻辑
};

ThreadPool.QueueUserWorkItem(callback);

优点
1.线程复用
2.可以限制最大线程数量,ThreadPool.SetMaxThreads(),ThreadPoolSetMinThreads

缺点
1.API太少,在线程等待和线程控制方面不足

3…NetFrameWork 3.0 Task

Task被称为多线程的最佳实践

Action action = ()=>
{
	//执行逻辑
};
Task task = new Task(action);
task.Start();

优点
1.Task线程全部是线程池线程
2.提供了丰富的API,适合开发实践

并行编程Parallel

Parallel.Invoke(action,action1,action2);

特点
1.Parallel可以启动多线程,主线程也参与计算,可以节约一个线程
2.Parallel可以指定最大的并发数量,通过设置MaxDegreeOfParallelism属性

Task应用

List task_list = new List();

task_list .Add(Task.Run(()=>方法1(参数1,参数2)));
task_list .Add(Task.Run(()=>方法2(参数1,参数2)));
task_list .Add(Task.Run(()=>方法3(参数1,参数2)));
task_list .Add(Task.Run(()=>方法4(参数1,参数2)));
task_list .Add(Task.Run(()=>方法5(参数1,参数2)));

//急需要多线程提升性能,又需要在多线程全部执行完成后才能执行的操作
Task.WaitAll(task_list.ToArray());

//阻塞当前线程,直到任一任务结束---会阻塞当前现场,直到任务全部结束
Task.WaitAny(task_list.ToArray());



//优化,不卡顿界面
List task_list = new List();
task_list .Add(Task.Run(()=>方法1(参数1,参数2)));
task_list .Add(Task.Run(()=>方法2(参数1,参数2)));
task_list .Add(Task.Run(()=>方法3(参数1,参数2)));
task_list .Add(Task.Run(()=>方法4(参数1,参数2)));
task_list .Add(Task.Run(()=>方法5(参数1,参数2)));

//但是不推荐这样
//1.尽量不要线程嵌套,容易出问题
//2.这全部是子线程完成,子线程不能直接操作界面
Task.Run(()=>
{
	//急需要多线程提升性能,又需要在多线程全部执行完成后才能执行的操作
	Task.WaitAll(task_list.ToArray());

	//阻塞当前线程,直到任一任务结束---会阻塞当前现场,直到任务全部结束
	Task.WaitAny(task_list.ToArray());
});


//更好方法
TaskFactory taskFactory = new TaskFactory();

//等任一任务完成后,启动新的Task完成后续动作
taskFactory .ContinueWhenAny(task_list.ToArray(),tArray=>
{
	//等待task_list执行完成后,执行的逻辑
});

//等全部任务完成后,启动新的Task完成后续动作
taskFactory.ContinueWhenAll(task_list.ToArray(),tArray=>
{
	//等待task_list执行完成后,执行的逻辑
});

Task.WaitAll,Task.WaitAny会阻塞当前线程,直到全部任务结束
Continue的后续线程可能是新线程,可能是刚完成任务的线程,还可能是同一线程,但不可能是主线程

多线程安全问题

for(int i=0;i<5;i++)
{
	Task.Run(()=>
	{
		Console.Writeline($"当前是{i}");
	});
}

//输出的i结果全为 5,并没有出现0,1,2,3,4
//修改如下
for(int i=0;i<5;i++)
{
	int j=i;//增加新变量
	Task.Run(()=>
	{
		Console.Writeline($"当前是{j}");
	});
}

//输入正常 0,1,2,3,4

多线程访问一个集合一般没有问题,线程安全问题一般都在修改一个对象的时候出现

List intList = new List();
for(int i=0;i<100000;i++)
{
	intList.Add(i);
}
Console.WriteLine(intList.Count);//输出结果为100000

//改成多线程
for(int i=0;i<100000;i++)
{
	Task.Run(()=>
	{
		intList.Add(i);
	});
}
Console.WriteLine(intList.Count);//输出结果小于100000,有数据丢失,出现线程安全问题
//原因:
//List是数组结构,在内存上是连续摆放,如果在同一时刻去增加一个数组,会存在同一时间操作同一内存位置,操作后会出现数据被覆盖

线程安全问题解决

加锁

//锁
private static readonly object LOCK = new object();

List intList = new List();

for(int i=0;i<100000;i++)
{
	Task.Run(()=>
	{
		lock(LOCK)
		{
			intList.Add(i);
		}
	});
}
Console.WriteLine(intList.Count);//输出结果为100000

//加lock可以解决线程安全问题,其实就是单线程化,lock保证方法块在任一时刻都是只有一个线程能操作,其他线程排队

Lock原理
Lock是一种语法糖,等价于Monitor,锁定一个内存引用,所以不能是值类型,也不能是null,因为需要占据一个引用

Lock相关测试

//新建类
public class TestLock
{
	public static readonly object TESTLOCK_LOCK = new object();
		
	public static void Show()
	{
		for(int i=0;i<5;i++)
		{
			Task.Run(()=>
				{
					lock(TESTLOCK_LOCK )
					{
						Console.Writeline($"开始{i} ");
						Thread.Sleep(2000);
						Console.Writeline($"结束{i}");
					}
				}
			});
		}
	}
}

TestLock.Show();

//外部多线程
for(int i=0;i<5;i++)
{
	Task.Run(()=>
		{
			lock(TestLock.TESTLOCK_LOCK )//使用的是同一个锁对象
			{
				Console.Writeline($"开始{i} ");
				Thread.Sleep(2000);
				Console.Writeline($"结束{i}");
			}
		}
	});
}
	
//使用同一对象锁,会等待同一个对象锁释放,会出现阻塞

锁 锁定的是一个内存引用

锁的使用和锁关键字this

//新建类
public class TestLockGeneric
{
	public static readonly object TESTLOCK_LOCK = new object();
		
	public static void Show(int index)
	{
		for(int i=0;i<5;i++)
		{
			Task.Run(()=>
				{
					lock(TESTLOCK_LOCK )
					{
						Console.Writeline($"开始{i}  TestLockGeneric {index}");
						Thread.Sleep(2000);
						Console.Writeline($"结束{i} TestLockGeneric {index}");
					}
				}
			});
		}
	}
}

TestLockGeneric.Show(1);
TestLockGeneric.Show(2);
TestLockGeneric.Show(3);


//单词调用内部是加锁顺序执行  
//1 ,2不能并发,因为是同一变量
//1 ,3能并发,因为是同变量,因为泛型类,在类型参数相同时,是同一个类;类型参数不同时,是不同的类

锁this变量

public class TestLock
{
	public void ShowThis(int index)
	{ 
		for(int i=0;i<5;i++)
			{
				Task.Run(()=>
					{
						lock(this)//this是当前变量
						{
							Console.Writeline($"开始{i}  ShowThis {index}");
							Thread.Sleep(2000);
							Console.Writeline($"结束{i} ShowThis{index}");
						}
					}
				});
			}
	}
}


TestLock lock1 = new TestLock();
lock1.ShowThis(1);

TestLock lock2 = new TestLock();
lock2.ShowThis(2);
//可以并发执行,因为在不同的变量里面This是不同的

await 、async

1.await/async是个新语法,出现在C#5.0 .NetFramwork 4.5以上 (CLR4.0)
2.是一个语法糖(编译器提供的新功能),不是一个全新的异步多线程使用方式
3.他本身不会产生新的线程,但是依托于Task而存在,所以程序执行时也是多线程的

语法

1.async可以随便添加,可以不用await
2.await只能出现在Task前面,但是方法必须声明async,不能单独出现
3.await/async之后原本没有返回值的,返回Task

你可能感兴趣的:(多线程,开发语言,后端)