线程与进程
线程与进程
Python GIL: 是在实现Python解析器(CPython)时所引入的一个概念。为了保证多线程访问数据的安全,CPython解释器只支持一个线程运行 所以即使是多线程的任务,实际上也只能是一个线程一个线程的去执行,并不是真正意义上的多线程。解决方案为多进程或协成处理。 计算密集型: 计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。 这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。 计算密集型任务由于主要消耗CPU资源,因此,代码运行效率至关重要。Python这样的脚本语言运行效率很低,完全不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。 IO密集型: 第二种任务的类型是IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务。 这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。 对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。 IO密集型任务执行期间,99%的时间都花在IO上,花在CPU上的时间很少,因此,用运行速度极快的C语言替换用Python这样运行速度极低的脚本语言,完全无法提升运行效率。 对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差。 1、如果任务是IO密集型任务,可以用多线程; 2、如果任务是计算密集型任务,可以通过改C语言解决
1、多线程任务
1、直接调用式多线程 import time import threading def music(func): #线程1任务函数 for i in range(2): print ("Begin listening to %s. %s" %(func,ctime())) sleep(4) print("end listening %s"%ctime()) def move(func): #线程2任务函数 for i in range(2): print ("Begin watching at the %s! %s" %(func,ctime())) sleep(5) print('end watching %s'%ctime()) threads = [] #线程列表 t1 = threading.Thread(target=music,args=('七里香',)) #创建线程对象,添加线程任务music,并给任务传递参数 threads.append(t1) #添加到线程列表 t2 = threading.Thread(target=move,args=('阿甘正传',)) #创建线程对象,添加线程任务move,并给任务传递参数 threads.append(t2) #添加到线程列表 if __name__ == '__main__': for t in threads: #循环线程列表 # t.setDaemon(True) #为当前线程开启守护,必须在start前配置,表示不等待当前线程运行结束,当主程序结束时立即停止该线程运行 t.start() #启动线程 # t.join() #线程启动后保持阻塞,待线程执行完毕再继续往下执行 print ("all over %s" %ctime()) #setDaemon(True): 将线程声明为守护线程,必须在start() 方法调用之前设置, 如果不设置为守护线程程序会被无限挂起。 这个方法基本和join是相反的。当我们 在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程 就分兵两路,分别运行,那么当主线程完成想退出时,会检验子线程是否完成。如 果子线程未完成,则主线程会等待子线程完成后再退出。 但是有时候我们需要的是 只要主线程完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以 用setDaemon方法啦 #join(): 在子线程完成运行之前,这个子线程的父线程将一直被阻塞。 thread 模块提供的其他方法: # threading.currentThread(): 返回当前的线程变量。 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。 # 除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法: # run(): 用以表示线程活动的方法。 # start():启动线程活动。 # join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。 # isAlive(): 返回线程是否活动的。 # getName(): 返回线程名。 # setName(): 设置线程名。
2、继承式多线程
import time
import threading
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)
#实例化线程1
t2 = MyThread(2)
#实例化线程2
t1.start()
#启动线程1,调用run方法
t2.start()#启动线程2,调用run方法
3、线程锁
import threading
def foo():
global num
#声明num为外部全局变量
r.acquire()
#开启线程锁,此段一直运行到关闭线程锁
temp = num
print('ok')
num = temp - 1
r.release()
#关闭线程锁,其他线程可使用num数据
num = 100
threadingList = []
r = threading.Lock()
#实例化线程锁对象
for i in range(10):
t = threading.Thread(target=foo)
t.start()
threadingList.append(t)
for t in threadingList:
t.join()#等待所有线程结束
print(num)
4、线程递归锁:入栈式加锁,先进后出
r = threading.RLock()
r.acquire()#加锁1
r.acquire()#加锁2
r.acquire()#加锁3
print('lock')
r.release()#解锁3
r.release()#解锁2
r.release()#解锁1
5、信号量:即并行锁,可同时加多把锁,每把锁销毁后可允许下个任务调用该锁
semaphore = threading.BoundedSemaphore(5)
#实例化5把锁
semaphore.acquire()
print('semaphore')
semaphore.release()
6、变量同步锁
有一类线程需要满足条件之后才能够继续执行,Python提供了threading.Condition对象用于条件变量线程的支持,它除了能提供RLock()或Lock()的方法外,
还提供了wait()、notify()、notifyAll()方法。
lock_con = threading.Condition([Lock/Rlock])#锁是可选选项,不传入锁,对象自动创建一个RLock().
wait():#条件不满足时调用,线程会释放锁并进入等待阻塞;
notify():#条件创造后调用,通知等待池激活一个线程;
notifyAll():#条件创造后调用,通知等待池激活所有线程。
import threading,time
from random import randint
class Producer(threading.Thread):
def run(self):
global L
while True:
val=randint(0,100)
print('生产者',self.name,":Append"+str(val),L)
if lock_con.acquire():
L.append(val)
lock_con.notify()
#这里通知wait状态的线程
lock_con.release()
time.sleep(3)
class Consumer(threading.Thread):
def run(self):
global L
while True:
lock_con.acquire()
if len(L)==0:
lock_con.wait()
#wait状态释放线程锁,并等待notify激活该wait状态,如果激活则从该线程加锁处继续执行,不会打印下面的test
print('test')
print('消费者',self.name,":Delete"+str(L[0]),L)
del L[0]
lock_con.release()
time.sleep(0.25)
if __name__=="__main__":
L=[]
lock_con=threading.Condition()
threads=[]
for i in range(5):
threads.append(Producer())
threads.append(Consumer())
for t in threads:
t.start()
for t in threads:
t.join()
7、同步条件Event
条件同步和条件变量同步差不多意思,只是少了锁功能,因为条件同步设计于不访问共享资源的条件环境。event=threading.Event():条件环境对象,初始值 为False;
event.isSet():返回event的状态值;
event.wait():如果 event.isSet()==False将阻塞线程;
event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
event.clear():恢复event的状态值为False。
import threading,time
class Boss(threading.Thread):
def run(self):
print("BOSS:今晚大家都要加班到22:00。")
event.isSet() or event.set()
time.sleep(5)
print("BOSS:<22:00>可以下班了。")
event.isSet() or event.set()
class Worker(threading.Thread):
def run(self):
event.wait()
print("Worker:哎……命苦啊!")
time.sleep(0.25)
event.clear()
event.wait()
print("Worker:OhYeah!")
if __name__=="__main__":
event=threading.Event()
threads=[]
for i in range(5):
threads.append(Worker())
threads.append(Boss())
for t in threads:
t.start()
for t in threads:
t.join()
2、队列
Python Queue模块有三种队列及构造函数: 1、Python Queue模块的FIFO队列先进先出。 class queue.Queue(maxsize) 2、LIFO类似于堆,即先进后出。 class queue.LifoQueue(maxsize) 3、还有一种是优先级队列级别越低越先出来。 class queue.PriorityQueue(maxsize) 此包中的常用方法(q = Queue.Queue()): q.qsize() 返回队列的大小 q.empty() 如果队列为空,返回True,反之False q.full() 如果队列满了,返回True,反之False q.full 与 maxsize 大小对应 q.get([block[, timeout]]) 获取队列,timeout等待时间 q.get_nowait() 相当q.get(False) 非阻塞 q.put(item) 写入队列,timeout等待时间 q.put_nowait(item) 相当q.put(item, False) q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号 q.join() 实际上意味着等到队列为空,再执行别的操作 import queue q = queue.Queue(maxsize = 10) #先进先出队列,默认为0,表示队列无限长 q.put('cat') #管道进入第一个成员 q.put('dog')#管道进入第二个成员 q.put('mouse',0)# 默认是1阻塞状态,管道满了再入则阻塞;设为0后管道满了则报异常 print(q.get())#cat ,管道第一个出列 print(q.get())#dog,管道第二个出列 print(q.get())#mouse,管道第三个出列 print(q.get(),0)# 默认是1阻塞状态,管道空了再取则阻塞;设为0后管道满了则报异常
进程
1、直接调用式多进程
import time from multiprocessing import Process #引入进程模块 def f(name): time.sleep(2) print('hello', name) if __name__ == '__main__': processList = [] for p in range(3): p = Process(target=f, args=('bob',)) #实例化进程对象,绑定进程任务函数和传递参数 p.start() #启动进程 processList.append(p) for p in processList: p.join()#防止僵尸进程,必须等待子进程结束
2、面向对象式多进程
import time from multiprocessing import Process #引入进程模块 class MyProcess(Process): def __init__(self,name): super(MyProcess,self).__init__()#先执行父类的init方法 self.name = name#self.name默认是进程名:MyProcess_1,传递来的name是对进程名重写命名 def run(self) time.sleep(2) print('hello', self.name) if __name__ == '__main__': #多进程时win下必须有本条件语句,Linux可选 processList = [] for i in range(3): p = MyProcess('bob') #实例化进程对象,绑定进程任务函数和传递参数 p.start() #启动进程 processList.append(p) for p in processList: p.join()#防止僵尸进程,必须等待子进程结束
3、多进程通信之消息Queues
from multiprocessing import Process, Queue def f(q,i): q.put([i]) #调用父进程消息q添加消息 if __name__ == '__main__': q = Queue()#创建父进程消息 processList = [] for i in range(3): p = Process(target=f, args=(q,i,)) #实例化子进程对象,绑定进程任务函数和传递参数消息q和变量 p.start() #启动进程 processList.append(p) for p in processList: p.join()#防止僵尸进程,必须等待子进程结束 print(q.get()) #获取消息信息 print(q.get()) #获取消息信息 print(q.get())#获取消息信息
4、多进程通信之管道Pipe
from multiprocessing import Process, Pipe def f(conn): conn.send('inset') #子进程给子进程发信息:inset print(conn.recv())#子进程接收父进程信息:outset conn.close() if __name__ == '__main__': parent_conn, child_conn = Pipe() #创建管道,返回父进程与子进程通信的两个端子 p = Process(target=f, args=(child_conn,)) #实例化子进程对象,并传参管道端子 p.start() print(parent_conn.recv())#父进程接受子进程信息:inset parent_conn.send('outset')#父进程给子进程发送outset p.join()
5、多进程通信之数据共享Manage
from multiprocessing import Process, Manager def f(d, l, i): d[i] = i#对主进程字典操作 l.append(i) #对主进程列表操作 if __name__ == '__main__': with Manager() as manager: #实例化manager对象,类似文件操作,自动关闭 d = manager.dict() #实例化字典对象 l = manager.list() #实例化列表对象 p_list = [] for i in range(10): p = Process(target=f, args=(d, l, i)) #实例化子进程并传递字典和列表参数 p.start() p_list.append(p) for res in p_list: res.join() print(d) #{2: 2, 1: 1, 4: 4, 0: 0, 3: 3, 6: 6, 5: 5, 9: 9, 7: 7, 8: 8} print(l)#[2, 1, 4, 0, 3, 6, 5, 9, 7, 8]
协程
1、greenlet
greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator from greenlet import greenlet def test1(): print(12) gr2.switch() #Switch切换,实例化对象每调用一次Switch方法,就执行一节到Switch语句 print(34) gr2.switch() def test2(): print(56) gr1.switch() print(78) gr1 = greenlet(test1) #实例化对象,类似迭代器,不是执行函数 gr2 = greenlet(test2) #实例化对象,类似迭代器,不是直线函数 gr1.switch()#执行gr1的Switch方法,输出12;然后调用gr2的Switch方法,输出56; #然后调用gr1的Switch方法,输出34;然后调用gr2的Switch方法,输出78
2、gevent
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。 import gevent def func1(): print('\033[31;1m李闯在跟海涛搞...\033[0m') gevent.sleep(2) #此时默认IO阻塞,跳转到其他任务 print('\033[31;1m李闯又回去跟继续跟海涛搞...\033[0m') def func2(): print('\033[32;1m李闯切换到了跟海龙搞...\033[0m') gevent.sleep(1) #此时默认IO阻塞,跳转到其他任务 print('\033[32;1m李闯搞完了海涛,回来继续跟海龙搞...\033[0m') gevent.joinall([ #通过joinall方法将两个任务联系起来,当一个任务阻塞住事,执行两一个任务 gevent.spawn(func1), #添加任务一 gevent.spawn(func2), #添加任务二,还可以加其他任务 ]) import gevent import urllib from gevent import monkey monkey.patch_all() #监听IO阻塞,随时切换任务 def f(url): print('GET:%s'%url) resp = urllib.urlopen(url) data = resp.read() with open('xiaohuar.html','wb') as fp: fp.write(data) gevent.joinall([ #多任务爬虫,若IO阻塞则切换任务 gevent.spawn(f,'www.xiaohuar.com'), gevent.spawn(f,'www.baidu.com'), ])
ng import Process, Queue def f(q,i): q.put([i]) #调用父进程消息q添加消息 if __name__ == '__main__': q = Queue()#创建父进程消息 processList = [] for i in range(3): p = Process(target=f, args=(q,i,)) #实例化子进程对象,绑定进程任务函数和传递参数消息q和变量 p.start() #启动进程 processList.append(p) for p in processList: p.join()#防止僵尸进程,必须等待子进程结束 print(q.get()) #获取消息信息 print(q.get()) #获取消息信息 print(q.get())#获取消息信息