什么叫多任务呢?简单地说,就是操作系统可以同时运行多个任务。打个比方,你一边在用浏览器上网,一边在听音乐,一边在用Word赶作业,这就是多任务,至少同时有3个任务正在运行。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。
现在,多核CPU已经非常普及了,但是,即使过去的单核CPU,也可以执行多任务。由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?
答案就是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。
真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。
线程thread:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
并发性:是指两个或多个事件在同一时间间隔内发生,。
并行性:是指两个或多个事件在同一时刻发生。
单线程执行任务程序模拟边唱歌,边玩游戏
"""Python多线程的使用"""importtimedefsing(name):foriinrange(3):print("%s正在唱歌...%d"%(name,i))time.sleep(1)defplay(name):foriinrange(3):print("%s正在玩游戏...%d"%(name,i))time.sleep(1)defsingle_thread_demo():"""单线程案例"""name=huising(name)play(name)defmain():single_thread_demo()if__name__==__main__:main()
运行结果如下:
hui正在唱歌...0hui正在唱歌...1hui正在唱歌...2hui正在玩游戏...0hui正在玩游戏...1hui正在玩游戏...2[Finishedin6.2s]
很显然程序并没有完成唱歌和玩游戏同时进行的要求,但生活中却很多这样的场景,例如边唱歌、边跳舞,唱跳一起进行。在Python中想实现多任务同时进行,可以使用多线程、多进程、协程等技术
Python多线程的实现常用模块Python线程中常用的两个模块为:
_threadthreading(推荐使用)Python的_thread模块是比较底层的模块,Python的threading模块是对_thread做了一些封装的,可以更加方便的被使用。
使用方式Python中使用线程有以下方式:
函数式_thread.start_new_thread()线程对象threading.Thread()自定义类继承threading.Thread_thread模块(函数式)_thread.start_new_thread(function,args[,kwargs])
参数说明:
function线程函数。args传递给线程函数的参数,他必须是个tuple类型。kwargs可选参数。"""Python多线程的使用"""importtimeimport_threaddefsing(name):foriinrange(3):print("%s正在唱歌...%d"%(name,i))time.sleep(1)defplay(name):foriinrange(3):print("%s正在玩游戏...%d"%(name,i))time.sleep(1)defsingle_thread_demo():"""单线程案例"""name=huising(name)play(name)deffun_multithread():"""函数式使用多线程"""name=hui#启动唱歌、玩游戏线程sing_thread=_thread.start_new_thread(sing,(name,))play_thread=_thread.start_new_thread(play,(name,))defmain():#single_thread_demo()fun_multithread()#防止主线程先执行完,导致子线程没有执行time.sleep(3)if__name__==__main__:main()
运行结果如下
第一种运行结果hui正在唱歌...0hui正在玩游戏...0hui正在唱歌...1hui正在玩游戏...1hui正在玩游戏...2hui正在唱歌...2[Finishedin3.1s]第二种运行结果hui正在唱歌...0hui正在玩游戏...0hui正在唱歌...1hui正在玩游戏...1hui正在唱歌...2hui正在玩游戏...2[Finishedin3.1s]第......
与单线程对比
hui正在唱歌...0hui正在唱歌...1hui正在唱歌...2hui正在玩游戏...0hui正在玩游戏...1hui正在玩游戏...2[Finishedin6.2s]
很明显使用多线程并发的操作,耗时更短,也完成了边唱歌边玩游戏的模拟。
注意:由于操作系统分给每个线程的时间片不一样,调度的先后顺序也不同,所以会有很多不一样的运行结果。
threading模块(线程对象)"""Python多线程的使用"""importtimeimport_threadimportthreadingdefsing(name):foriinrange(3):print("%s正在唱歌...%d"%(name,i))time.sleep(1)defplay(name):foriinrange(3):print("%s正在玩游戏...%d"%(name,i))time.sleep(1)defthread_obj_demo():"""使用线程对象创建多线程"""name=hui#创建线程sing_thread=threading.Thread(target=sing,args=(name,))play_thread=threading.Thread(target=play,args=(name,))#启动线程sing_thread.start()play_thread.start()defmain():thread_obj_demo()#防止主线程先执行完,导致子线程没有执行time.sleep(3)if__name__==__main__:main()
运行结果如下
hui正在唱歌...0hui正在玩游戏...0hui正在玩游戏...1hui正在唱歌...1hui正在唱歌...2hui正在玩游戏...2[Finishedin3.1s]threading模块常用方法threading.currentThread():返回当前的线程变量。threading.enumerate():返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
"""pythonthreading模块的常用方法"""importtimeimportthreadingdeftest1():print(------test1-------)time.sleep(3)deftest2():print(------test2-------)time.sleep(3)defmain():t1=threading.Thread(target=test1)t2=threading.Thread(target=test2)t1.start()t2.start()print(activeCount:%d%threading.activeCount())print(threading.enumerate())whilethreading.activeCount()!=1:time.sleep(1)print(threading.enumerate())print(threading.enumerate())if__name__==__main__:main()
运行结果
------test1-------------test2-------activeCount:3[_MainThread(MainThread,started),Thread(Thread-1,started),Thread(Thread-2,started)][_MainThread(MainThread,started),Thread(Thread-1,started),Thread(Thread-2,started)][_MainThread(MainThread,started),Thread(Thread-1,started),Thread(Thread-2,started)][_MainThread(MainThread,started)][_MainThread(MainThread,started)]threading.Thread类构造
当线程对象一但被创建,其活动一定会因调用线程的start()方法开始。这会在独立的控制线程调用run()方法。
classthreading.Thread(group=None,target=None,name=None,args=(),kwargs={},*,daemon=None)
调用这个构造函数时,必需带有关键字参数。参数如下:
group应该为None;为了日后扩展ThreadGroup类实现而保留。
target是用于run()方法调用的可调用对象。默认是None,表示不需要调用任何方法。
name是线程名称。默认情况下,由"Thread-N"格式构成一个唯一的名称,其中N是小的十进制数。
args是用于调用目标函数的参数元组。默认是()。
kwargs是用于调用目标函数的关键字参数字典。默认是{}。
如果不是None,daemon参数将显式地设置该线程是否为守护模式。如果是None(默认值),线程将继承当前线程的守护模式属性。
如果子类型重载了构造函数,它一定要确保在做任何事前,先发起调用基类构造器(Thread.__init__())。
threading.Thread类方法及属性start()
开始线程活动。它在一个线程里最多只能被调用一次。它安排对象的run()方法在一个独立的控制进程中调用。如果同一个线程对象中调用这个方法的次数大于一次,会抛出RuntimeError。
run()
代表线程活动的方法。你可以在子类型里重载这个方法。标准的run()方法会对作为target参数传递给该对象构造器的可调用对象(如果存在)发起调用,并附带从args和kwargs参数分别获取的位置和关键字参数。
join(timeout=None)
等待,直到线程终结。这会阻塞调用这个方法的线程,直到被调用join()的线程终结--不管是正常终结还是抛出未处理异常--或者直到发生超时,超时选项是可选的。当timeout参数存在而且不是None时,它应该是一个用于指定操作超时的以秒为单位的浮点数(或者分数)。因为join()总是返回None,所以你一定要在join()后调用is_alive()才能判断是否发生超时--如果线程仍然存活,则join()超时。当timeout参数不存在或者是None,这个操作会阻塞直到线程终结。一个线程可以被join()很多次。如果尝试加入当前线程会导致死锁,join()会引起RuntimeError异常。如果尝试join()一个尚未开始的线程,也会抛出相同的异常。
name
只用于识别的字符串。它没有语义。多个线程可以赋予相同的名称。初始名称由构造函数设置。
getName()
setName()
旧的name取值/设值API;直接当做特征属性使用它。
ident
这个线程的线程标识符,如果线程尚未开始则为None。这是个非零整数。参见get_ident()函数。当一个线程退出而另外一个线程被创建,线程标识符会被复用。即使线程退出后,仍可得到标识符。
is_alive()
返回线程是否存活。当run()方法刚开始直到run()方法刚结束,这个方法返回True。模块函数enumerate()返回包含所有存活线程的列表。
daemon
一个表示这个线程是(True)否(False)守护线程的布尔值。一定要在调用start()前设置好,不然会抛出RuntimeError。初始值继承于创建线程;主线程不是守护线程,因此主线程创建的所有线程默认都是daemon=False。当没有存活的非守护线程时,整个Python程序才会退出。
isDaemon()
setDaemon()
旧的name取值/设值API;建议直接当做特征属性使用它。
自定义类继承threading.Thread通过使用threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,往往会定义一个新的子类class,只要继承threading.Thread就可以了,然后重写run方法
"""Python多线程的使用"""importtimeimportthreadingclassMyThread(threading.Thread):#def__init__(self):#super().__init__()defrun(self):foriinrange(3):time.sleep(1)msg="Im"+self.name+
+str(i)#name属性中保存的是当前线程的名字print(msg)defmain():t=MyThread()t.start()if__name__==__main__:main()如果子类型重载了构造函数,它一定要确保在做任何事前,先发起调用基类构造器(Thread.__init__())。
super().__init__()#需先调用父类构造
运行结果如下:
ImThread-1
0ImThread-11ImThread-12结论:
python的threading.Thread类有一个run()方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。而创建自己的线程实例后,通过Thread类的start()方法,可以启动该线程,交给python虚拟机进行调度,当该线程获得执行的机会时,就会调用run()方法执行线程
总结每个线程默认有一个名字,尽管上面的例子中没有指定线程对象的name,但是python会自动为线程指定一个名字。当线程的run()方法结束时该线程完成。无法控制线程调度程序,但可以通过别的方式来影响线程调度的方式。