今天来聊一下线程相关的话题,多线程,网上一搜一大把,但始终没有去总结和自我沉淀,这里起个头just do it,然后一直完善下去吧。
【名词概念】
线程:进程中负责程序执行的执行单元。一个进程中至少有一个线程。(定义为程序的执行路径)
多线程:解决多任务同时执行的需求,合理使用CPU资源。多线程的运行是根据CPU切换完成,如何切换由CPU决定,因此多线程运行具有不确定性。
线程池:基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。
线程五大状态:新建、就绪、运行、阻塞、死亡
阻塞的原因:1.sleep() 2.I/O阻塞 3.锁 4.等待某个触发条件
线程锁:最常用的是读写锁,就是读写互斥、写写互斥、读读共享。
【为什么要使用多线程?】
1.现实异步
2.节省CPU周期的浪费,提高应用程序的效率,提高资源利用率。
【如何使用?】
一、System.Threading.Tasks.Task
- Task.Run(() => do());
- Task.Factory.StartNew(() => do());
匿名函数形式
二、System.Threading.Tasks.Parallel(提供了For和Foreach系列方法)
实例场景:根据班级Id列表获取各个班级成员的全部信息(含学生姓名和成绩,老师个人信息等),这个执行时间较长所以考虑使用多线程也提高接口效率。代码如下图~
值得参考点
1.不固定创建多少线程数,而是根据所需来灵活创建,实例中是按10取模(若有22个班级,则创建3个线程来同时进行,这可以自己根据业务调整)
2.在线程循环体外申明一个变量,在内部进行写操作锁。
从上面图中不难看出,出现了一个关键字lock,是因为当多线程出现时,往list中读写是随机并发的所以可能是错乱的,我们称之为“线程不安全”,所以这里加了锁!那么有没有list是线程安全的,无需加锁也能多线程下资源共用呢?答案是有的。
多线程下请使用System.Collections.Concurrent命名空间下的集合类型,ConcurrentDictionary<TKey, TValue> 是并发安全的数据结构,其引入的目的也是为了解决高并发场景下字典数据的共享。
关于ConcurrentDictionary的【坑】
ConcurrentDictionary本质是一个字典,所以是不允许出现重复键的,那么有个方法GetOrAdd,有则获取无则创建,但多线程居然有可能相同key进入此方法体多次!是不是很诡异,说好的线程安全的?!
实战案例和解决方案如下图
图一是线程不安全的,图二是线程安全的。
【问题剖析】
ConcurrentDictionary 确实是线程安全,只不过它的线程安全有一定的限制。
即,并发字典只保证存入字典的值是线程安全的,也就是说虽然执行了两次匿名方法但最终仅有一个值能被放入字典,其它值将会被丢弃。(所以你的方法体内是不在乎线程顺序的)
场景再现:比如多人报名(可能会出现重复报名),GetOrAdd方法体内执行报名函数,那么你就会相同人重复报名;再延申下,如果在缓存场景,这就相当于缓存被穿透了,无法避免高并发时 (为了更新缓存) 对后端数据服务的高频访问。
字典的值设为Lazy,即只有唯一的一个 (延迟加载的) 值会被放入字典,不关心顺序,存在即可。
当然,根据具体业务看是否合适,这里仅分享踩过的坑~
今天就先写到这里吧,后续坚持完善下去。