Python学习第44天(线程、join和setDaemon)
昨天大致说了进程的概念,通过cpu在进程之间的切换,计算机实现了你同时上网,同时进行听音乐的需求,然后今天就要说说线程,其实线程的本质就是微缩化的进程。
先引出一下线程是为了满足什么需求进行提出的:我们电脑中常用的txt记事本,我们在日常使用的过程中,你录入了一段文字,记事本这个程序同时帮你显示在显示屏,同时完成了录入磁盘保存的功能,这部分在实现的过程中做了三件事情:
1.接受键盘的录入信息;
2.将内容信息显示在显示屏上;
3.将录入的信息保存到硬盘上。
这个时候需求就有了,如何让三步同时进行呢,我之前写的貌似都是一个单路径的模式(除了socketserver模块部分),就是先完成write,然后是read,最后save,这样反馈到实际生活的过程就是,你不断的录入一串信息,但是你本人什么都看不见,等你全部输入完了,电脑会显示你输入的内容,这个时候你还没法进行修改,因为已经顺道存到磁盘中了,这明显和我们实际生活中的情况是不一样的。
假如用进程的方法进行解决:三项任务三个进程,cpu在三个任务至今进行不断切换,貌似能够解决问题,但是存在两个非常明显的缺陷:
1.进程是独享一部分内存的,也就是说输入、保存和现实要在三个不同内存区域内进行运行;
2.基于缺陷1导致的问题就是,在进程之间要进行数据通信,这是一项十分耗费内存的过程,相同的信息不断的在cpu内来回拷贝,还要切换进程,非常的不划算
基于上面的这项需求,我们引入了线程:
线程:程序内部的一个微缩进程,共享整个进程的所有内存资源,随之带来的两个优势:
1.不存在内部信息通讯的概念,共用进程的内存信息
2.因为共用内存,所以切换更为方便,不想进程那么麻烦
加上昨晚的进程概念,我们可以得到以下结论:
进程:程序的最小资源单元
线程:程序的最小执行单元
线程的出现是为了降低上下文切换的消耗,提高系统的并发性,并突破一个进程只能干一样事的缺陷,使到进程内并发成为可能。
假设,一个文本程序,需要接受键盘输入,将内容显示在屏幕上,还需要保存信息到硬盘中。若只有一个进程,势必造成同一时间只能干一样事的尴尬(当保存时,就不能通过键盘输入内容)。若有多个进程,每个进程负责一个任务,进程A负责接收键盘输入的任务,进程B负责将内容显示在屏幕上的任务,进程C负责保存内容到硬盘中的任务。这里进程A,B,C间的协作涉及到了进程通信问题,而且有共同都需要拥有的东西-------文本内容,不停的切换造成性能上的损失。若有一种机制,可以使任务A,B,C共享资源,这样上下文切换所需要保存和恢复的内容就少了,同时又可以减少通信所带来的性能损耗,那就好了。是的,这种机制就是线程。
线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合和堆栈共同组成。线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能。线程没有自己的系统资源。
一个程序至少有一个进程,一个进程至少有一个线程.(进程可以理解成线程的容器)进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈)但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.
好了,说了这么多,如何来实现多线程呢(卧槽,之前的全是单线程)
今天的重点(threading模块)
threading 模块建立在thread 模块之上。thread模块以低级、原始的方式来处理和控制线程,而threading 模块通过对thread进行二次封装,提供了更方便的api来处理线程。
听了两天内容,一行代码都木得,也是怪心慌的,现在,我就不心慌了
先说一下旧的模式
import time def listen(num): print('listen is coming at %s'%time.ctime()) time.sleep(3) print('listen is ending at %s'%time.ctime()) def game(num): print('game is coming at %s'%time.ctime()) time.sleep(5) print('game is ending at %s'%time.ctime()) if __name__ == '__main__': listen(1) game(1) print('ending time is %s'%time.ctime())
此时的输出结果(程序由前向后依次运行):
listen is coming at Mon Apr 6 23:59:52 2020
listen is ending at Mon Apr 6 23:59:55 2020
game is coming at Mon Apr 6 23:59:55 2020
game is ending at Tue Apr 7 00:00:00 2020
ending time is Tue Apr 7 00:01:47 2020
此时我们一如threading模块并使用其Thread类进行实例:
import time import threading def listen(num): print('listen is coming at %s'%time.ctime()) time.sleep(3) print('listen is ending at %s'%time.ctime()) def game(num): print('game is coming at %s'%time.ctime()) time.sleep(5) print('game is ending at %s'%time.ctime()) if __name__ == '__main__': t1 = threading.Thread(target = listen , args = (1,)) t1.start() t2 = threading.Thread(target = game , args = (2,)) t2.start() print('ending time is %s'%time.ctime())
此时的运行结果你会发现,主线程上的print ending和game、listen的一块开始运行了
listen is coming at Tue Apr 7 00:04:32 2020
game is coming at Tue Apr 7 00:04:32 2020
ending time is Tue Apr 7 00:04:32 2020
listen is ending at Tue Apr 7 00:04:35 2020
game is ending at Tue Apr 7 00:04:37 2020
此时的程序,主线程上又多了两个分支与主线程同时开始运行
那么join又是什么意思呢?
看运行结果就能明白
import time import threading def listen(num): print('listen is coming at %s'%time.ctime()) time.sleep(3) print('listen is ending at %s'%time.ctime()) def game(num): print('game is coming at %s'%time.ctime()) time.sleep(5) print('game is ending at %s'%time.ctime()) if __name__ == '__main__': t1 = threading.Thread(target = listen , args = (1,)) t1.start() t1.join() t2 = threading.Thread(target = game , args = (2,)) t2.start() print('ending time is %s'%time.ctime())
运行顺序如下,此时ending一直等到listen运行结束才与game一块开始运行
所以意思你懂的,就是再把他绑回到主线程上
如果上面的程序在t2.start()后面也加上t2.join(),上面就等于和最初的是一个样子
下面是setDaemon(乍一看,set哆啦A梦)
import time import threading def listen(num): print('listen is coming at %s'%time.ctime()) time.sleep(3) print('listen is ending at %s'%time.ctime()) def game(num): print('game is coming at %s'%time.ctime()) time.sleep(5) print('game is ending at %s'%time.ctime()) if __name__ == '__main__': t1 = threading.Thread(target = listen , args = (1,)) t1.setDaemon(True) t1.start() t2 = threading.Thread(target = game , args = (2,)) t2.setDaemon(True) t2.start() print('ending time is %s'%time.ctime())
此时的输出结果我第一看也是惊呆了,老说这个是守护线程,咋守护,请看好:
listen is coming at Tue Apr 7 00:13:23 2020
game is coming at Tue Apr 7 00:13:23 2020
ending time is Tue Apr 7 00:13:23 2020
主线程运行结束后,其他的线程就不运行了,就是这么的厉害。所以叫守护,这部分内容好像还比较多,今晚来不及全部听完了,明天继续补充了。
join():在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
setDaemon(True):
将线程声明为守护线程,必须在start() 方法调用之前设置, 如果不设置为守护线程程序会被无限挂起。这个方法基本和join是相反的。当我们 在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程 就分兵两路,分别运行,那么当主线程完成想退出时,会检验子线程是否完成。如 果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是 只要主线程完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以 用setDaemon方法啦
一些threading模块的常用方法
# run(): 线程被cpu调度后自动执行线程对象的run方法 # start():启动线程活动。 # isAlive(): 返回线程是否活动的。 # getName(): 返回线程名。 # setName(): 设置线程名。 threading模块提供的一些方法: # threading.currentThread(): 返回当前的线程变量。 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
今天的内容就是这些了,知道了线程之后瞬间感觉自己之前的low爆了
刚扫了一眼熬夜的危害,不行,我明天上班有好多事情要做,得抓紧睡了,拜拜。