1.什么是多任务?
打个比方,你一边在用浏览器上网,一边在听MP3,
一边在用Word赶作业,这就是多任务,同时有3个任务正在运行。
还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。
2.单核CPU是怎么执行多任务的呢?
操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。
扩展:
–单核CPU,操作系统轮流让各个任务交替执行,(并发)
–多核CUP, 并行执行多任务只能在多核CPU上实现,(并行)
3.进程:Process
对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。
4.线程:Thread
有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。
5.多任务实现方式:
多进程模式;
多线程模式;
多进程+多线程模式
6.线程小结:
1.线程是最小的执行单元,而进程由至少由一个线程组成。
如何调度进程和线程,完全由操作系统决定,
程序自己不能决定什么时候执行,执行多长时间。
2.多任务 可以由多进程完成,也可以由一个进程内的多线程完成。
3.由于线程是操作系统直接支持的执行单元,因此,高级语言通常
都内置多线程的支持,Python也不例外,并且,Python的线程是
真正的Posix(可移植性操作系统接口)线程,而不是模拟出
来的线程。
4.多线程多用于i/o密集型,多进程多用于计算密集型:
– 计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多
– IO密集型指的是系统的CPU性能相对硬盘、内存要好很多
– IO 就是接口 (input输入就是一种接口)
7.线程的实现模块:
_thread和threading,_thread是低级模块,
threading是高级模块,对_thread进行了封装。
绝大多数情况下,我们只需要使用threading这个高级模块。
1.前言引入:
-- from time import sleep
-- def sing():
-- for i in range(3):
-- print("正在唱歌...%d"%i)
-- sleep(1)
-- def dance():
-- for i in range(3):
-- print("正在跳舞...%d"%i)
-- sleep(1)
-- if __name__ == '__main__':
-- sing() #唱歌3次
-- dance() #跳舞3次
很显然刚刚的程序并没有完成唱歌和跳舞同时进行的要求
2.(了解)
– 第一种:_thread
例1:
import _thread
from time import sleep, ctime
def loop0():
print('loop0开始:', ctime())
sleep(2)
print('loop0结束:', ctime())
def loop1():
print('loop1开始:', ctime())
sleep(1)
print('loop1结束:', ctime())
def main():
_thread.start_new_thread(loop0, ())
_thread.start_new_thread(loop1, ())
sleep(3) # 防止主线程结束,子线程还在运行
if __name__ == '__main__':
main()
3.第二种:threading
threading.activeCount(): 返回正在运行的线程数量。
threading.currentThread(): 返回当前的线程变量。
线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:
run(): 用以表示线程活动的方法。
start():开启线程。
join([time]): 设置join之后,主线程等待子线程全部执行完成后
或者子线程超时后,主线程才结束。
isAlive(): 返回线程是否活动的。
getName(): 返回线程名。
setName(): 设置线程名。
例1 单线程:
import time
def tongZhi():
print("同学们,考试要带准考证和身份证")
time.sleep(1)
if __name__ == "__main__":
for i in range(5):
tongZhi()
例1 多线程:
import threading
import time
def tongZhi():
print("同学们,考试要带准考证和身份证")
time.sleep(1)
if __name__ == "__main__":
for i in range(5):
t = threading.Thread(target=tongZhi)
t.start() # 启动线程,即让线程开始执行
-- 1.可以明显看出使用了多线程并发的操作,花费时间要短很多
-- 2.创建好的线程,需要调用start()方法来启动
-- 3.主线程会等待所有的子线程结束后才结束
4.查看线程数量:threading.activeCount()
import threading
from time import sleep,ctime
def sing():
for i in range(3):
print("正在唱歌...%d"%i)
sleep(1)
def dance():
for i in range(3):
print("正在跳舞...%d"%i)
sleep(1)
if __name__ == '__main__':
print('---开始---:%s'%ctime())
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
while True:
# 1.返回正在运行的线程数量
length = threading.activeCount()
# 返回一个包含正在运行的线程的list
# length = len(threading.enumerate())
print('当前运行的线程数为:%d'%length)
if length<=1:
break
sleep(0.5)
5.join的用法:
import threading
import time
def action(arg):
time.sleep(1)
print ('the thread name is:%s' % threading.currentThread().getName())
print ('the arg is:%s' %arg)
time.sleep(1)
thread_list = [] #线程存放列表
for i in range(4):
t =threading.Thread(target=action,args=(i,))
thread_list.append(t)
for t in thread_list:
t.start()
# t.join() # 错误用法,每个线程都被上一个线程的join阻塞
for t in thread_list:
t.join() # 告诉父线程,等着子线程
-- 解释:t1.setDaemon(True)的操作,将父线程设置为了守护线程。
-- 根据setDaemon()方法的含义,父线程打印内容后便结束了,不管子线程是否执行完毕了。
-- 这基本和join是相反的
import threading
import time
class MyThread(threading.Thread):
def __init__(self,id):
threading.Thread.__init__(self)
def run(self):
time.sleep(5)
print("777777 ")
if __name__ == "__main__":
t1=MyThread(999)
t1.setDaemon(True)
t1.start()
print("I am the father thread.")
6.run()和start() 方法的区别:(了解,演示run()和start())
-- 1.用start()方法来启动线程,真正实现了多线程运行
-- 2.run()方法只是类的一个普通方法而已,如果直接调用Run方法,
-- 程序中依然只有主线程这一个线程。
-- import threading
-- class myThread(threading.Thread):
-- def __init__(self, threadID, name, counter):
-- threading.Thread.__init__(self)
-- self.threadID = threadID
-- self.name = name
-- self.counter = counter
-- def run(self):
-- currentTreadname = threading.currentThread()
-- print ("running in", currentTreadname)
-- thread = myThread(1,"mythread",1)
-- thread.run()
-- thread.start()
9.线程优缺点:
多线程优点:
多线程类似于同时执行多个不同程序,多线程运行有如下优点:
1、使用线程可以把占据 耗时的任务 放到后台去处理。
2、用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件。
的处理,可以弹出一个进度条来显示处理的进度。
3、程序的运行速度可能加快。
4、在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程
就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。
多线程缺点:
1、开启大量线程消耗资源。
2、线程之间数据是共享的,容易出现数据混乱的情况,加锁麻烦。
10.出现全局变量混乱的问题:
假设两个线程t1和t2都要对全局变量g_num(默认是0)进行加1运算,
t1和t2都各对g_num加1百万次,g_num的最终的结果应该为2百万。
但是由于是多线程同时操作,有可能出现下面情况:
1.在g_num=0时,t1取得g_num=0。此时系统把t1调度为”waiting”状态,
把t2转换为”running”状态,t2也获得g_num=0。
2.然后t2对得到的值进行加1并赋给g_num,使得g_num=1。
3.然后系统又把t2调度为”waiting”,把t1转为”running”。
线程t1又把它之前得到的0加1后赋值给g_num。
4.这样导致虽然t1和t2都对num加1,但结果仍然是g_num=1。
例1:
import threading
import time
g_num = 0
def work1(num):
global g_num
for i in range(num):
g_num += 1
print("----in work1, num is %d---" % g_num)
def work2(num):
global g_num
for i in range(num):
g_num += 1
print("----in work2, num is %d---" % g_num)
print("---线程创建之前g_num is %d---" % g_num)
t1 = threading.Thread(target=work1, args=(100,)) # 改为1000000
t1.start()
-- t1.join()
t2 = threading.Thread(target=work2, args=(100,)) # 改为1000000
t2.start()
-- t2.join()
# 功能相当于join,等待子线程结束
while threading.activeCount() != 1:
time.sleep(1)
print("2个线程对同一个全局变量操作之后的最终结果是:%s" % g_num)
11.解决全局变量混乱的问题:(使用线程锁,排队上洗手间)
import threading
import time
g_num = 0
def test1(num):
global g_num
for i in range(num):
# True表示堵塞 即如果这个锁在上锁之前已经被上锁了,那么这个线程会在这里一直等待到解锁为止
# False表示非堵塞,即不管本次调用能够成功上锁,都不会卡在这,而是继续执行下面的代码
mutexFlag = mutex.acquire(True)
if mutexFlag: # 如果上锁成功
g_num += 1
mutex.release() # 解锁
print("---test1---num=%d"%g_num)
def test2(num):
global g_num
for i in range(num):
mutexFlag = mutex.acquire(True) # True表示堵塞
if mutexFlag:
g_num += 1
mutex.release()
print("---test2---num=%d"%g_num)
#创建一个互斥锁
#这个所默认是未上锁的状态
mutex = threading.Lock()
p1 = threading.Thread(target=test1, args=(1000000,))
p1.start()
p2 = threading.Thread(target=test2, args=(1000000,))
p2.start()
# 功能相当于join,等待子线程结束
while threading.activeCount() != 1:
time.sleep(1)
print("2个线程对同一个全局变量操作之后的最终结果是:%s" % g_num)
11.全局解释器锁:GIL
1.多核CPU:(引言,了解)
如果你拥有一个多核CPU,你肯定在想,多核应该可以同时执行多个线程。
如果写一个死循环的话,会出现什么情况呢?
打开Mac OS X的Activity Monitor,或者Windows的Task Manager,都可以监控某个进程的CPU使用率。
我们可以监控到一个死循环线程会100%占用一个CPU。
如果有两个死循环线程,在多核CPU中,可以监控到会占用200%的CPU,也就是占用两个CPU核心。
要想把N核CPU的核心全部跑满,就必须启动N个死循环线程。
试试用Python写个死循环:
import threading, multiprocessing
from multiprocessing import Process
def loop():
x = 0
while True:
x = x ^ 1 # 按位异或运算符:当两对应的二进位相异时,结果为1
# 此时计算密集型,多线程起不到作用
for i in range(multiprocessing.cpu_count()):
t = threading.Thread(target=loop)
t.start()
启动与CPU核心数量相同的N个线程,在4核CPU上可以监控到CPU占用率仅有100%,也就是仅使用了一核。
但是用C、C++或Java来改写相同的死循环,直接可以把全部核心跑满,4核就跑到400%,8核就跑到800%,为什么Python不行呢?
2.因为Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:
Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,
然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。
这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,
即使100个线程跑在100核CPU上,也只能用到1个核。
3.GIL是Python解释器设计的历史遗留问题,通常我们用的解释器是官方实现的CPython,
要真正利用多核,除非重写一个不带GIL的解释器。
所以,在Python中,可以使用多线程,但不要指望能有效利用多核。
如果一定要通过多线程利用多核,那只能通过C扩展来实现,不过这样就失去了Python简单易用的特点。
不过,也不用过于担心,Python虽然不能利用多线程实现多核任务,
但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。
12总结:
线程定义。
线程的方法。
线程优缺点。
Lock()
GIL
13(了解) 死锁: --就是互相等待,程序
#coding=utf-8
import threading
import time
class MyThread1(threading.Thread):
def run(self):
if mutexA.acquire():
print(self.name+'----do1---up----')
time.sleep(1)
if mutexB.acquire():
print(self.name+'----do1---down----')
mutexB.release()
mutexA.release()
class MyThread2(threading.Thread):
def run(self):
if mutexB.acquire():
print(self.name+'----do2---up----')
time.sleep(1)
if mutexA.acquire():
print(self.name+'----do2---down----')
mutexA.release()
mutexB.release()
mutexA = threading.Lock()
mutexB = threading.Lock()
if __name__ == '__main__':
t1 = MyThread1()
t2 = MyThread2()
t1.start()
t2.start()
14.将类中的函数放在线程中执行,且能控制线程的结束
import time
import threading
class DownThread:
def __init__(self):
self._running = True
def terminate(self):
self._running = False
def run(self, n):
while self._running and n > 0:
print('T-minus', n)
n -= 1
time.sleep(1)
if __name__ == '__main__':
c = DownThread()
t = threading.Thread(target=c.run, args=(10,))
t.start()
time.sleep(3)
c.terminate()
t.join()