线程与进程
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())#获取消息信息