多线程类似于同时执行多个不同程序,多线程运行有如下优点:
- 使用线程可以把占据长时间的程序中的任务放到后台去处理。
- 用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度
- 程序的运行速度可能加快
- 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。
线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。
指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。
- 线程可以被抢占(中断)。
- 在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) -- 这就是线程的退让。
线程可以分为:
- 内核线程:由操作系统内核创建和撤销。
- 用户线程:不需要内核支持而在用户程序中实现的线程。
Python3 线程中常用的两个模块为:
- _thread
- threading(推荐使用)
thread 模块已被废弃。用户可以使用 threading 模块代替。所以,在 Python3 中不能再使用"thread" 模块。为了兼容性,Python3 将 thread 重命名为 "_thread"。
在这里我们主要讲解的是threading模块。
_thread 提供了低级别的、原始的线程以及一个简单的锁,它相比于 threading 模块的功能还是比较有限的。
threading 模块除了包含 _thread 模块中的所有方法外,还提供的其他方法:
- threading.currentThread(): 返回当前的线程变量。
- threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
- threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:
- run(): 用以表示线程活动的方法。
- start():启动线程活动。
- join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
- isAlive(): 返回线程是否活动的。
- getName(): 返回线程名。
- setName(): 设置线程名。
在我们理解多线程之前,我们先来看一下单线程。
参考链接:http://www.cnblogs.com/fnng/p/3670789.html
单线程:
假设在很多年前,我想听音乐和看电影一定要先排一下顺序的。
from time import ctime,sleep def music(): for i in range(2): print("I was listening to music. %s" % ctime()) sleep(1) def move(): for i in range(2): print("I was at the movies. %s" % ctime()) sleep(5) if __name__ == '__main__': music() move() print("all over %s" % ctime())
我们先听了一首音乐,通过for循环来控制音乐的播放了两次,每一首音乐需要播放1秒钟,sleep()来控制音乐播放的时长。接着又看了一场电影,每一场电影需要5秒钟,我们通过for循环看了两遍。听完歌和看完电影以后,通过print("all over %s" % ctime())看了一下当前时间,准备结束这个娱乐活动。
运行结果:
I was listening to music. Tue Aug 1 17:08:15 2017 I was listening to music. Tue Aug 1 17:08:16 2017 I was at the movies. Tue Aug 1 17:08:17 2017 I was at the movies. Tue Aug 1 17:08:22 2017 all over Tue Aug 1 17:08:27 2017
如果我们把music()和move()看作是音乐播放器和视频播放器的话,我们要播放什么歌曲或者电影就应该由我们自己来决定。所以,就把以上代码修改为:
from time import ctime,sleep def music(func): for i in range(2): print("I was listening to %s. %s" % (func, ctime())) sleep(1) def move(func): for i in range(2): print("I was at the %s. %s" % (func, ctime())) sleep(5) if __name__ == '__main__': music('十年') move('战狼') print("all over %s" % ctime())
这样我们就对music()和move()进行了传参处理。
运行结果:
I was listening to 十年. Tue Aug 1 17:17:22 2017 I was listening to 十年. Tue Aug 1 17:17:23 2017 I was at the 战狼. Tue Aug 1 17:17:24 2017 I was at the 战狼. Tue Aug 1 17:17:29 2017 all over Tue Aug 1 17:17:34 2017
多线程:
跟着时代的发展,科技的进步,由于我们程序猿的幸苦工作,CPU也就越来越快了,我们就可以同时干多件事情,所以我们一边coding一边听歌的时代到来。
我们继续对于以上的例子进行改变,引入threadring来同时播放音乐和视频:
import threading from time import ctime,sleep def music(func): for i in range(2): print("I was listening to %s. %s" % (func, ctime())) sleep(1) def move(func): for i in range(2): print("I was at the %s. %s" % (func, ctime())) sleep(5) # 创建了threads数组 把创建好的线程t1装到threads数组中。 threads = [] # 创建线程t1,使用threading.Thread()方法,在这个方法中调用music方法target=music,args方法对music进行传参。 t1 = threading.Thread(target=music, args=('十年',)) # 把创建好的线程t1装到threads数组中 threads.append(t1) # 接着以同样的方式创建线程t2,并把t2也装到threads数组。 t2 = threading.Thread(target=move, args=('战狼',)) threads.append(t2) if __name__ == '__main__': for t in threads: t.setDaemon(True) t.start() print("all over %s" % ctime())
setDaemon()
setDaemon(True)将线程声明为守护线程,必须在start() 方法调用之前设置,如果不设置为守护线程程序会被无限挂起。子线程启动后,父线程也继续执行下去,当父线程执行完最后一条语句print("all over %s" % ctime())后,没有等待子线程,直接就退出了,同时子线程也一同结束。
start()
开始线程活动。
运行结果:
I was listening to 十年. Tue Aug 1 17:31:29 2017 I was at the 战狼. Tue Aug 1 17:31:29 2017 all over Tue Aug 1 17:31:29 2017
从执行结果来看,子线程(muisc 、move )和主线程(print("all over %s" % ctime()))都是同一时间启动,但由于主线程执行完结束,所以导致子线程也终止。
继续调整程序:
... if __name__ == '__main__': for t in threads: t.setDaemon(True) t.start() t.join() print("all over %s" % ctime())
我们只对上面的程序加了个join()方法,用于等待线程终止。join()的作用是,在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
PS: join()方法的位置是在for循环外的,也就是说必须等待for循环里的两个进程都结束后,才去执行主进程。
运行结果:
I was listening to 十年. Tue Aug 1 20:24:03 2017 I was at the 战狼. Tue Aug 1 20:24:03 2017 I was listening to 十年. Tue Aug 1 20:24:04 2017 I was at the 战狼. Tue Aug 1 20:24:08 2017 all over Tue Aug 1 20:24:13 2017
从执行结果可看到,music 和move 是同时启动的。
开始时间24分03秒,直到调用主进程为24分13秒,总耗时为10秒。从单线程时减少了2秒,我们可以把music的sleep()的时间调整为4秒。
...
def music(func):
for i in range(2):
print("I was listening to %s. %s" % (func, ctime()))
sleep(4)
...
运行结果:
I was listening to 十年. Tue Aug 1 20:34:59 2017 I was at the 战狼. Tue Aug 1 20:34:59 2017 I was listening to 十年. Tue Aug 1 20:35:03 2017 I was at the 战狼. Tue Aug 1 20:35:04 2017 all over Tue Aug 1 20:35:09 2017
子线程启动34分59秒,主线程运行35分09秒。
虽然music每首歌曲从1秒延长到了4秒 ,但通过多程线的方式运行脚本,总的时间没变化。
class threading.Thread()说明:
class threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
这个构造函数通常会用一些关键字参数,下面我们了解下这些关键字:
group :这个变量作为保留变量,是为了以后扩展用的,暂时可以不用考虑。
target: 是通过run()方法调用的可调用对象。默认为无,这意味着什么都不做。
name:线程的名字。默认情况下,一个唯一的名称是”thread-n”,的形式,其中n是一个小的十进制数。
args:元组参数,为target所调用的。
kwargs:关键字参数的字典,为target所调用的。
daemon: 设置daemon是否daemon 如果没有显示设置,daemon的属性时从当前线程继承。
如果子类重写此构造函数,它必须确保在做别的事情之前调用基类的构造函数(thread.__init__()。
线程有两种调用方式:
1.直接调用
import threading import time def sayhi(num): #定义每个线程要运行的函数 print("running on number:%s" %num) time.sleep(3) if __name__ == '__main__': t1 = threading.Thread(target=sayhi,args=(1,)) #生成一个线程实例 t2 = threading.Thread(target=sayhi,args=(2,)) #生成另一个线程实例 t1.start() #启动线程 t2.start() #启动另一个线程 print(t1.getName()) #获取线程名 print(t2.getName())
2.继承式调用
import threading import time class MyThread(threading.Thread): def __init__(self,num): threading.Thread.__init__(self) self.num = num def run(self):#定义每个线程要运行的函数 print("running on number:%s" % self.num) time.sleep(3) if __name__ == '__main__': t1 = MyThread(1) t2 = MyThread(2) t1.start() t2.start()