跟我一起学“仓颉”编程语言-线程管理

跟我一起学“仓颉”编程语言-线程管理 一、线程和进程程序是静态的代码集合当程序运行时对应的程序实例就是进程。进程是操作系统分配资源的基本单位。线程是操作系统能够进行运算调度的最小单位。线程被包含于进程之中是进程中实际执行单位。二、并发和并行并发指的是多个任务交替执行并行指的是多个任务同时执行。单核处理器通过时间片轮转的方式实现并发。在任意时刻单核处理器只能执行一个任务其他任务只能等待它是通过线程切换使得每个任务都在短时间内被执行但是任务之间的切换非常快从而给我们一种“同时执行”的错觉。多核处理器是可以做到真正意义上的并行的两个或两个以上的线程可以同时在不同的处理器上执行。多线程的应用场景Web应用服务器、图形用户界面程序、游戏开发、数据分析等三、同步同步是协调多个一起执行线程确保它们在访问公共资源或执行相关任务时能够以有序和安全的方式进行的机制。注意这里的“一起”指代前面的并发和并行无论是并发还是并行任务都需要以某种形式的协调和同步以确保对公共资源的访问是有序和安全的。同步的目的保证数据一致性和完整性、控制执行顺序、避免死锁和活锁、提高程序的稳定性和可靠性。四、线程的创建仓颉中的线程是用户态的轻量级线程并且支持抢占。用户态线程的创建、调度和管理都在用户空间完成不需要操作系统内核的介入这样可以降低上下文切换的成本提升系统性能。轻量级比传统的操作系统内核线程更加轻巧占用资源小因此可以实现在应用程序中创建大量的线程实现更细力度的并发控制。抢占式调度线程调度器可以在任何时刻中断线程的执行将CPU资源分配给其他线程这样可以防止某个线程长期占用CPU导致其他线程饥饿。单个线程的创建当仓颉程序运行时会创建一个进程并在进程内创建一个主线程。主线程从main函数开始执行如果需要同时处理多个任务可以创建自己的线程相对于主线程自己创建的线程叫子线程子线程内的还可以创建子线程。使用spawn表达式创建线程package Study main () { spawn { for(i in 0..3) { println(我是子线程: ${i}) } } for(i in 0..3) { println(我是主线程: ${i}) } }注意主线程和子线程是随机交替执行的。五、线程的生命周期1. 就绪状态当一个线程被创建后该线程就进入到就绪状态。该线程准备好运行并等待CPU时间片。2. 运行状态当线程被线程调度器选中就会获得CPU时间片并且开始执行当运行态的线程因为时间片用完而被抢占时线程调度器就会中断该线程的执行将其变为就绪状态。3. 阻塞状态当运行状态的线程因为某种原因而被阻塞时线程调度器会中断该线程变为阻塞状态同时线程调度器会从就绪状态的线程里选择一个并分配时间片。当处于阻塞的线程在阻塞结束后就会变成就绪状态在执行调度后才会变成运行状态。4. 结束状态处于运行的线程在任务执行完或者取消后就会变为结束状态。在进行多线程编程时需要考虑主线程的生命周期和主线程对子线程的生命周期影响。主线程的生命周期通常决定着整个程序的生命周期当主线程结束后应用程序就结束了。package Study main () { spawn { // 阻塞子线程3秒 sleep(Duration.second * 3) for(i in 0..3) { println(我是子线程: ${i}) } } for(i in 0..3) { println(我是主线程: ${i}) } }如果子线程在执行关键代码的时候被强制终止会导致不可弥补的损失因此可以通过使用确保主线程等待所有子线程结束后在结束的代码。package Study main () { spawn { for(i in 0..3) { println(我是子线程: ${i}) } } // 阻塞主线程3秒 sleep(Duration.second * 3) for(i in 0..3) { println(我是主线程: ${i}) } }子线程和子线程之间是并列关系即它们之间不会直接产生依赖。package Study main () { spawn { println(我是子线程1) spawn { // 阻塞子线程2 3秒 sleep(Duration.second * 3) println(我是子线程2) } println(子线程1执行结束) } // 阻塞主线程3秒 sleep(Duration.second * 3) println(我是主线程) }六、Future类型Future类型是泛型类它是spawn表达式的返回值类型。通过sleep函数来模拟线程阻塞是不严谨的因为无法预测子线程的执行时间为了避免这个问题我们可以使用get函数。package Study main () { let future: FutureUnit spawn { for (i in 0..3) { println(我是子线程: ${i}) } println(子线程运行结束) } // 阻塞主线程 future.get() println(我是主线程) }使用get函数还可以获取线程的返回值。package Study main () { let future: FutureInt64 spawn { var sum 50 20 sum } // 阻塞主线程 let result future.get() println(子线程返回: ${result}) }package Study main () { let future: FutureInt64 spawn { var result 1 for (i in 1..100) { result * i } result } // 阻塞主线程1纳秒若1纳秒后子线程没有执行完则就是None执行完成则有返回值 let result future.get(Duration.nanosecond) println(子线程返回: ${result}) }tryGet函数不会阻挡当前线程而且它的返回结果是Option类型。package Study main () { let future: FutureInt64 spawn { var result 1 for (i in 1..10) { result * i } result } // tryGet函数会尝试获取子线程返回值不阻塞主线程 let optResult future.tryGet() if (optResult.isNone()) { println(tryGet函数调用时, 子线程还没结束) } else { println(子线程结果: ${optResult.getOrThrow()}) } }Thread类通过它可以查看线程的id、名称等信息。Thread类无法直接通过构造函数得到对象可以通过Thread类的静态成员属性currentThread获取当前线程Thread对象或者通过Future对象的属性thread获取Future对象对应的线程Thread对象。package Study import std.collection.* main () { let futureList ArrayListFutureUnit() // 创建三个线程 for (i in 0..3) { let future spawn { Thread.currentThread.name 线程${i} println(${Thread.currentThread.name}执行中) } // 将子线程放入线程集合中 futureList.add(future) } for (future in futureList) { // 阻塞主线程 future.get() println(${future.thread.name}执行结束) } }Future类提供了cancel函数可以让对应的线程发送终止请求可以使用Thread类的hasPendingCancellation属性判断线程是否存在取消请求。package Study main () { let future spawn { for (i in 0..3) { // 如果当前线程取消就终止并跳出循环 if (Thread.currentThread.hasPendingCancellation) { println(线程终止) break } // 模拟子线程执行任务 sleep(Duration.second * 3) println(第${i}个线程执行结束) } } // 阻塞主线程3秒 sleep(Duration.second * 3) // 子线程终止请求 future.cancel() // 阻塞主线程直到future对应的子线程结束 future.get() }七、小结本章为大家详细的介绍了仓颉编程语言中线程管理的内容下一章为大家带来原子操作的内容。最后创作不易如果大家觉得我的文章对学习仓颉服务端开发有帮助的话就动动小手点个免费的赞吧收到的赞越多我的创作动力也会越大哦谢谢大家