Python使用threading实现多线程
Python中多任务的实现可以使用进程,也可以使用线程。
一、线程介绍
进程是操作系统分配程序执行资源的单位,而线程是进程的一个实体,是CPU调度和分配资源的单位。
一个程序运行起来至少有一个进程,一个进程中至少有一个线程。
CPU分配给线程,即CPU真正运行的是线程中的代码。
分配CPU给线程时,是通过时间片轮询的方式进行的,即多个线程同一时间并没有真正的同时执行,而是CPU快速的在线程之间切换,所以看起来是所有线程“同时”执行一样。
python的threading模块对底层的thread做了封装,可以方便的使用,通过threading模块来创建线程。
二、实例化Thread对象来创建多线程
from threading import Thread, enumerateimport timedef play_game():for i in range(5):print('Playing game!', end=' | ')time.sleep(1)def listen_song():for i in range(5):print('Listening song!', end=' | ')time.sleep(1)if __name__ == '__main__':start = time.time()play_game()listen_song()end = time.time()print('\n', enumerate())print('One thread cost time: ', end - start)multi_start = time.time()t1 = Thread(target=play_game)t2 = Thread(target=listen_song)t1.start()t2.start()print('\n', enumerate())while len(enumerate()) != 1:time.sleep(1)multi_end = time.time()print('\nMulti thread cost time: ', multi_end - multi_start)
运行结果:
Playing game! | Playing game! | Playing game! | Playing game! | Playing game! | Listening song! | Listening song! | Listening song! | Listening song! | Listening song! | [<_MainThread(MainThread, started 2)>]One thread cost time: 10.004258155822754Playing game! | Listening song! | [<_MainThread(MainThread, started 2)>, <Thread(Thread-1, started 15992)>, <Thread(Thread-2, started 22720)>]Playing game! | Listening song! | Playing game! | Listening song! | Playing game! | Listening song! | Playing game! | Listening song! | Multi thread cost time: 6.003507852554321
threading模块是跨平台和版本的多线程模块,提供了一个Thread类来创建线程对象。创建子线程时,只需要传入一个需要执行的函数和函数的参数,创建一个Thread实例,用start()方法启动这个实例。
在上面的代码中,我们创建了两个函数play_game和listen_song。实现多线程时,实例化了两个Thread类的对象t1和t2,t1和t2就是线程对象,将需要执行的函数传给target参数,再用t1和t2对象的start()方法开启子线程。
play_game和listen_song函数是两个需要执行的任务,这时候有两个任务。两个任务都在主线程中执行时,花了10秒多的时间,创建两个子进程来执行play_game和listen_song函数时,花了6秒多的时间。
在我们创建了两个子线程时,有三个线程在执行任务,主线程其实是没有耗时的,只是我们将主线程阻塞了,真正耗时的其实是执行任务的两个子线程。
创建子线程之后,子线程同时处理任务,这说明我们实现了多线程处理多任务,即多个任务是“同时”执行的。
三、继承Thread类来实现多线程
创建一个新的类,继承Thread类,将要执行的代码写到run方法里面。
from threading import Thread, enumerateimport time# 自定义类,继承Threadclass OurThread(Thread):def run(self):for i in range(3):print("Play game " + self.name, end=' | ')time.sleep(1)if __name__ == '__main__':# 通过MyThread创建线程对象o1 = OurThread()o2 = OurThread()print(enumerate())o1.start()o2.start()print('\n', enumerate())
运行结果:
[<_MainThread(MainThread, started 19616)>]Play game Thread-1 | Play game Thread-2| [<_MainThread(MainThread, started 19616)>, <OurThread(Thread-1, started 19608)>, <OurThread(Thread-2, started 17556)>]Play game Thread-1 | Play game Thread-2 | Play game Thread-1 | Play game Thread-2 |
python的threading.Thread类有一个run方法,用于定义线程的方法,可以在自己的线程类中覆盖该方法。
创建自己的线程实例后,通过Thread类的start方法,可以启动该线程,当该线程获得执行的机会时,就会调用run方法执行线程。
上面的代码中,我们自定义了一个类OurThread,并重写了run方法,在run方法中写我们需要执行的代码。实例化了两个OurThread类的对象o1和o2,o1和o2都是线程对象,我们通过start()方法来开启线程。
四、Thread语法结构和常用方法
Thread([group [, target [, name [, args [, kwargs]]]]])
1.target:传递一个函数的引用,可以认为这个子线程就是执行这个函数的代码,这里传的是函数的引用,后面不能有小括号
2.args:给target指定的函数传递的参数,以元组的方式传递,这里必须是一个元组,如果不是元组会报TypeError,只有一个参数时要注意别出错
3.kwargs:给target指定的函数传递关键字参数,以字典的方式传递,这里必须是一个字典
4.name:给进程设定一个名字,可以不设定
5.group:指定进程组,大多数情况下用不到
Thread的常用方法:
1.start():启动子线程实例
2.is_alive():判断子线程是否还在活着
3.join([timeout]):是否等待子线程执行结束,或等待多少秒
4.run():用于定义线程的方法,可以在自己的线程类中覆盖该方法
Thread的常用属性:
1.name:当前线程的别名,默认为Thread-N,N为从1开始递增的整数
线程的执行:
1.开启:当调用thread.start()时开启线程,再运行线程中的代码
2.结束:子线程把target指向的函数中的语句执行完毕后,或者线程中的run方法代码执行完毕后,立即结束当前子线程
3.查看当前线程数量:通过threading.enumerate()可枚举当前运行的所有线程
4.主线程何时结束:所有子线程执行完毕后,主线程才结束
五、多线程的执行顺序
from threading import Threadimport timeclass OurThread(Thread):def run(self):for i in range(3):print("Play game {} ".format(i) + self.name)time.sleep(1)if __name__ == '__main__':for i in range(3):o = OurThread()o.start()
运行结果:
Play game 0 Thread-1Play game 0 Thread-2Play game 0 Thread-3Play game 1 Thread-2Play game 1 Thread-1Play game 1 Thread-3Play game 2 Thread-2Play game 2 Thread-1Play game 2 Thread-3
多线程的执行是无序的。
上面的代码中,我们创建了3个线程,每个线程都会执行三次print,可以看到,多个线程之间的执行是没有严格的先后顺序的。
六、进程、线程对比
1.进程和线程都能够完成多任务。
2.进程是操作系统进行资源分配和调度的一个基本单位。
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,但是它可以与同一个进程中的其他的线程共享进程所拥有的全部资源。
3.线程的划分尺度小于进程(资源比进程少),使得多线程程序的并发性高。
4.进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
5.线程不能够独立执行,必须依存在进程中。
6.线程执行开销小,但不利于资源的管理和保护,而进程正相反。