进程 线程 协程

  • 一、进程的概念

假如有两个程序A和B,首先执行A程序的时候遇到数据阻塞(A程序在等待数据的输入),那么这个时候CPU就是空闲的,我们为了充分利用资源能不能先将A程序撂一边,先执行B程序,等到A程序接收到数据再将B程序暂停继续执行A程序呢?答案显然是可以的,这个时候有一个关键词:切换。也就是这就是CPU从A切换到B,再从B切换到A。

那么既然是切换,那么就涉及到状态的保存,状态的恢复,以及程序A与程序B所占用的资源(数据,CPU,键盘等等),自然而然就需要有一个东西可以去记录A程序和B程序的资源,以及如何去识别A和B,这个时候就有了一个进程的抽象。

进程定义

进程就是一个程序在一个数据集运行的一次动态执行过程

进程一般由 程序、数据集和进程控制块 三部分组成

我们编写的程序用来描述进程要完成的功能以及如何完成;

数据集则是程序在执行过程中用到的资源;

进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。

 

  • 二、线程的概念

线程的出现是为了降低上下文切换的消耗,提高系统的并发性,并突破一个进程只能干一样事的缺陷, 使到进程内并发成为可能。

假设,一个文本程序,需要接受键盘输入,将内容显示在屏幕上,还需要保存信息到硬盘中。若只有 一个进程,势必造成同一时间只能干一样事的尴尬(当保存时,就不能通过键盘输入内容)。若有多 个进程,每个进程负责一个任务,进程A负责接收键盘输入的任务,进程B负责将内容显示在屏幕上的 任务,进程C负责保存内容到硬盘中的任务。这里进程A,B,C间的协作涉及到了进程通信问题,而且 有共同都需要拥有的东西-------文本内容,不停的切换造成性能上的损失。若有一种机制,可以使 任务A,B,C共享资源,这样上下文切换所需要保存和恢复的内容就少了,同时又可以减少通信所带 来的性能损耗,那就好了。是的,这种机制就是线程。

线程定义

线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序 计数器、寄存器集合和堆栈共同组成。

线程的引入减小了程序并发执行时的开销,提高了操作系统的并发 性能。线程没有自己的系统资源。

 

  • 三、进程和线程的关系区别

1 一个程序至少有一个进程,一个进程至少有一个线程.(进程可以理解成线程的容器)

2 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

3 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和 程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

4 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调 度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈)但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源. 一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.

5.进程是最小的资源单位,线程是最小的执行单位

 

  • 四、为何要开多线程

多线程指的是,在一个进程中开启多个线程,简单的讲:如果多个任务共用一块地址空间,那么必须在一个进程内开启多个线程。详细的讲分为4点:

  1. 多线程共享一个进程的地址空间

      2. 线程比进程更轻量级,线程比进程更容易创建可撤销,在许多操作系统中,创建一个线程比创建一个进程要快10-100倍,在有大量线程需要动态和快速修改时,这一特性很有用

      3. 若多个线程都是cpu密集型的,那么并不能获得性能上的增强,但是如果存在大量的计算和大量的I/O处理,拥有多个线程允许这些活动彼此重叠运行,从而会加快程序执行的速度。

      4. 在多cpu系统中,为了最大限度的利用多核,可以开启多个线程,比开进程开销要小的多。(这一条并不适用于python)

 

  • 五、线程

(1)线程的两种调用方式

直接调用

 1 import threading,time
 2 def hi(num):
 3     start_time = time.time()
 4     print('say hi %d'%num)
 5     time.sleep(3)
 6     stop_time = time.time()
 7     print('总共用的时间是%s'%(stop_time-start_time))
 8 
 9 if __name__ == '__main__':
10     t1 = threading.Thread(target=hi,args=(10,))
11     t1.start()
12     t2 = threading.Thread(target=hi, args=(9,))
13     t2.start()
14     # say hi 10
15     # say hi 9
16     # 总共用的时间是3.000171422958374
17     # 总共用的时间是3.001171588897705
直接调用

   自定义threading类调用

 1 import threading,time
 2 class Mythreading(threading.Thread):
 3     def __init__(self,num):
 4         threading.Thread.__init__(self)
 5         self.num = num
 6     def run(self):      ##  必须要重写run()方法
 7         start_time = time.time()
 8         print('say hi %d'%self.num)
 9         time.sleep(3)
10         stop_time = time.time()
11         print('总共用的时间是%s'%(stop_time-start_time))
12 
13 if __name__ == '__main__':
14     t = Mythreading(10)
15     t.start()
16     time.sleep(2)
17     print('ending....')
18     # say hi 10
19     # ending....
20     # 总共用的时间是3.000171422958374
继承调用

 

(2)join方法

join方法:

1.join方法的作用是阻塞主进程(挡住,无法执行join以后的语句)专注执行多线程。

2.多线程多join的情况下,依次执行各线程的join方法,前头一个结束了才能执行后面一个。

3.无参数,则等待到该线程结束,才开始执行下一个线程的join。

 1 import threading,time
 2 def music():
 3     print('start to music %s'%time.ctime())
 4     time.sleep(3)
 5     print('stop to music %s'%time.ctime())
 6 
 7 def game():
 8     print('start to play game %s'%time.ctime())
 9     time.sleep(5)
10     print('stop to play game %s'%time.ctime())
11 
12 if __name__ == '__main__':
13     t1 = threading.Thread(target=music)
14     t1.start()
15     t2 = threading.Thread(target=music)
16     t2.start()
17     t1.join()       ##  这里加了join方法,也就是说t1没有执行完后面的代码就不走了
18     print('ending.....')
19     # start to music Fri Apr 10 23:50:07 2020
20     # start to music Fri Apr 10 23:50:07 2020
21     # stop to music Fri Apr 10 23:50:10 2020
22     # ending.....
23     # stop to music Fri Apr 10 23:50:10 2020
join方法

 

(3)setDaemon方法

setDaemon(True):

setDaemon一定要在start方法之前加,设置为守护线程。结果和join方法相反

主线程执行完要看子线程执行结束情况,如果子线程还没结束,那么主线程就需要等待子线程结束再一起退出。这时子线程1如果设置成守护线程,那么主线程执行结束就直接无视子线程1了,不需要等子线程1了。 

 1 import threading,time
 2 def music():
 3     print('start to music %s'%time.ctime())
 4     time.sleep(3)
 5     print('stop to music %s'%time.ctime())
 6 
 7 def game():
 8     print('start to play game %s'%time.ctime())
 9     time.sleep(5)
10     print('stop to play game %s'%time.ctime())
11 
12 if __name__ == '__main__':
13     t1 = threading.Thread(target=music)
14     t2 = threading.Thread(target=game)
15     t2.setDaemon(True)      ##  setDaemon 必须在start()前设置
16     t1.start()
17     t2.start()
18     print('ending....',time.ctime())
19     # start to music Sat Apr 11 10:23:22 2020
20     # start to play game Sat Apr 11 10:23:22 2020
21     # ending.... Sat Apr 11 10:23:22 2020
22     # stop to music Sat Apr 11 10:23:25 2020
setDaemon守护线程

 

(4)其它方法

 1 # run():  线程被cpu调度后自动执行线程对象的run方法
 2 # start():启动线程活动。
 3 # isAlive(): 返回线程是否活动的。
 4 # getName(): 返回线程名。
 5 # setName(): 设置线程名。
 6 
 7 threading模块提供的一些方法:
 8 # threading.currentThread(): 返回当前的线程变量。
 9 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
10 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
其他方法

 

 

  • 五、并行&并发  同步&异步

并发&并行
并行:系统具有同时处理多个任务(动作)的能力,通俗点就是多核CPU各自同一时刻运行着不同的应用。

并发:系统具有处理多个任务(动作)的能力,通俗点来说就是单核CPU可以做到来回切换处理QQ音乐、暴风影音、微信等应用的能力,但是要注意一点,这不是同一时刻单核CPU可以处理多个应用,而是因为切换速度很快,让你感知不到这个切换。

并发和并行的关系:并行是并发的子集

 

同步&异步

同步:执行过程中遇到IO阻塞(等待外部数据)时,选择------------等:不继续接下来的操作,直到等到了数据再继续接下来的操作---例如:打电话

异步:                        ----------不等:继续其它操作,直到数据到了在继续刚刚的操作---例如:发短信

 

 

  • 六、GIL(全局解释器锁)

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

上面的核心意思就是,无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许同一个进程的一个线程运行

 

假设我们需要完成两个功能,累加和类乘,现在分别用串行和多线程来计算运行所用的时间

 1 import threading,time
 2 start = time.time()
 3 def add():
 4     num = 0
 5     for i in range(100000000):
 6         num += i
 7     print('add运行结束了')
 8 
 9 def mul():
10     num = 1
11     for i in range(1,100000):
12         num *= i
13     print('mul运行结束了')
14 
15 add()
16 mul()
17 print('ending。。。')
18 print('消耗的时间是:',(time.time()-start))
19 # add运行结束了
20 # mul运行结束了
21 # ending。。。
22 # 消耗的时间是: 13.369764566421509
计算密集型--串行
 1 import threading,time
 2 start = time.time()
 3 def add():
 4     num = 0
 5     for i in range(100000000):
 6         num += i
 7     print('add运行结束了')
 8 
 9 def mul():
10     num = 1
11     for i in range(1,100000):
12         num *= i
13     print('mul运行结束了')
14 
15 t1 = threading.Thread(target=add)
16 t2 = threading.Thread(target=mul)
17 t1.start()
18 t2.start()
19 t1.join()
20 t2.join()
21 print('ending。。。')
22 print('消耗的时间是:',(time.time()-start))
23 # mul运行结束了
24 # add运行结束了
25 # ending。。。
26 # 消耗的时间是: 13.208755731582642
计算密集型--多线程

 

可以看到,串行用到的时间是(13.369764566421509),多线程所用的时间是(13.208755731582642)。这次用到的Python解释器是3.7,在这里已经优化很多了。如果这是在2.7中运行的话,多线程运行的时间要多于串行所用的时间,这是为什么?

这是因为Python解释器中加了一把GIL锁,正因为有这把锁,同一时刻同一个进程中只允许有一个线程在运行,那么也就是在计算中不能用两个CPU来处理两个函数

我们可以看到上面的过程,add函数和mul函数在争夺锁出去运行,可能先是add拿到权限了,出去运行了一会儿时间轮循(也就是过一段时间就要退出CPU)进行切换,add回去了,锁被mul拿到了,运行了一会儿又回来了,add又出去继续运行。。。这样一个过程需要大量的切换。发过来我们看,如果是串行的话,运行完add自然就轮到mul运行了,这中间没有切换的过程,所以效率肯定要比多线程的要快。

 

总结:

1、任务分为:IO密集型计算密集型

2、对于IO密集型:Python的 多线程 是有效的,也可以用进程+协程

  对于计算密集型:Python就不适用了,Python没有比较有效的方法适用计算密集型的任务。

 

 

  • 七、同步锁

我们现在有一个需求:用一百个线程来做一个累减的操作

 1 import threading,time
 2 num = 100
 3 def num_reduce():
 4     global num
 5     temp = num
 6     time.sleep(0.001)
 7     num = temp-1
 8 
 9 t_list = []
10 for i in range(100):
11     t = threading.Thread(target=num_reduce)
12     t.start()
13     t_list.append(t)
14 for j in t_list:
15     j.join()
16 
17 print(num)      ##  79  78  76  79  76
累减操作

 

可以看到,结果并不唯一,为什么结果不是0?

这是因为,假如有一百个人来代表一百个线程,首先第一个人易烊千玺拿到num=100,然后进入IO阻塞,释放出锁,这时第二个人胡歌拿到锁了,拿到的num仍然是100,进入IO阻塞,释放锁,第三个人蔡徐坤拿到锁,拿到的num仍然是100.。。。可能到第八个人的时候易烊千玺time.sleep结束了,num变为99,这时第八个人王俊凯进来拿到的num就是99了。。。这个过程下去

我们来看一张图

归根到底,就是因为线程共用数据资源,那我们把资源共用的那一部分锁住不就行了吗,这个时候就用到同步锁了

 1 import threading,time
 2 num = 100
 3 lock = threading.Lock()     ##  创建一个同步锁对象
 4 def num_reduce():
 5     global num
 6     lock.acquire()      ##  上锁
 7     temp = num
 8     time.sleep(0.001)
 9     num = temp-1
10     lock.release()      ##  放锁
11 
12 t_list = []
13 for i in range(100):
14     t = threading.Thread(target=num_reduce)
15     t.start()
16     t_list.append(t)
17 for j in t_list:
18     j.join()
19 
20 print(num)     ##   0
同步锁 Lock

 

 

  • 八、死锁和递归锁

有两个人犬痣聋和蔡徐坤,犬痣聋手里拿着一个篮球,蔡徐坤手里拿着一个足球,蔡徐坤想要犬痣聋手里的篮球,而犬痣聋想要蔡徐坤手里的足球,谁也不愿意先给对方,两人就这样僵持着。

其实线程也会出现这种情况,就是死锁

 1 import threading,time
 2 
 3 Lock_A = threading.Lock()
 4 Lock_B = threading.Lock()
 5 
 6 class Mythread(threading.Thread):
 7     def sectionA(self):
 8         Lock_A.acquire()
 9         print('%s拿到Lock_A'%self.name)
10         time.sleep(2)
11 
12         Lock_B.acquire()
13         print('%s拿到Lock_B'%self.name)
14         time.sleep(1)
15 
16         Lock_B.release()
17         Lock_A.release()
18 
19     def sectionB(self):
20         Lock_B.acquire()
21         print('%s拿到Lock_B' % self.name)
22         time.sleep(2)
23 
24         Lock_A.acquire()
25         print('%s拿到Lock_A' % self.name)
26         time.sleep(1)
27 
28         Lock_A.release()
29         Lock_B.release()
30 
31     def run(self):
32         self.sectionA()
33         self.sectionB()
34 
35 if __name__ == '__main__':
36     t_list = []
37     for i in range(5):
38         t = Mythread()
39         t.start()
40         t_list.append(t)
41     for j in t_list:
42         j.join()
43 
44     print('ending。。。')
45     
46     # Thread-1拿到Lock_A
47     # Thread-1拿到Lock_B
48     # Thread-1拿到Lock_B
49     # Thread-2拿到Lock_A
死锁

 

造成死锁的原因是线程2拿到了A锁,在等着拿B锁,而线程1拿到了B锁,在等着拿A锁,两个线程谁也不让着谁,那就只能干耗着

那我们很容易就想到解决死锁的办法:我把A锁和B锁绑定在一起,拿到A锁就只有我能拿B锁,其他人都不能拿,这不就解决了吗?

这就是递归锁(RLock)

 1 import threading,time
 2 
 3 l_Lock = threading.RLock()      ##  递归锁对象
 4 
 5 class Mythread(threading.Thread):
 6     def sectionA(self):
 7         l_Lock.acquire()        ##  account = 1
 8         print('%s拿到Lock_A'%self.name)
 9         time.sleep(2)
10 
11         l_Lock.acquire()        ##  account = 2
12         print('%s拿到Lock_B'%self.name)
13         time.sleep(1)
14 
15         l_Lock.release()        ##  account = 1
16         l_Lock.release()        ##  account = 0
17 
18     def sectionB(self):
19         l_Lock.acquire()
20         print('%s拿到Lock_B' % self.name)
21         time.sleep(2)
22 
23         l_Lock.acquire()
24         print('%s拿到Lock_A' % self.name)
25         time.sleep(1)
26 
27         l_Lock.release()
28         l_Lock.release()
29 
30     def run(self):
31         self.sectionA()
32         self.sectionB()
33 
34 if __name__ == '__main__':
35     t_list = []
36     for i in range(5):
37         t = Mythread()
38         t.start()
39         t_list.append(t)
40     for j in t_list:
41         j.join()
42 
43     print('ending。。。')
44     # Thread-1拿到Lock_A
45     # Thread-1拿到Lock_B
46     # Thread-1拿到Lock_B
47     # Thread-1拿到Lock_A
48     # Thread-3拿到Lock_A
49     # Thread-3拿到Lock_B
50     # Thread-3拿到Lock_B
51     # Thread-3拿到Lock_A
52     # Thread-5拿到Lock_A
53     # Thread-5拿到Lock_B
54     # Thread-2拿到Lock_A
55     # Thread-2拿到Lock_B
56     # Thread-4拿到Lock_A
57     # Thread-4拿到Lock_B
58     # Thread-4拿到Lock_B
59     # Thread-4拿到Lock_A
60     # Thread-2拿到Lock_B
61     # Thread-2拿到Lock_A
62     # Thread-5拿到Lock_B
63     # Thread-5拿到Lock_A
64     # ending。。。
递归锁 RLock

 

递归锁实现流程:

Thread1拿到递归锁(RLock),这时递归锁的计时器标识account=1,其它人就只能等着,只有account=0时其他人才能抢锁。 

 

  • 九、同步对象(event)

事件是一个简单的同步对象;事件表示内部标志,线程可以等待标志被设置,或者自己设置或清除标志。

创建一个同步对象:event = threading.Event()

客户端线程可以等待标志被设置

event.wait()

服务器线程可以设置或重置它

event.set()
event.clear()

如果设置了标志,wait方法不会做任何事情。如果标志被清除,wait将阻塞,直到它再次被设置。任何数量的线程都可能等待相同的事件。

通俗点就是如果一个线程A设置为同步对象,另外几个线程有wait()方法,那么这些线程就得等待线程A

 1 import threading,time
 2 class Boss(threading.Thread):
 3     def run(self):
 4         print("BOSS:今晚大家都要加班到22:00。")
 5         print(event.isSet())      ## False,wait后面不能走
 6         event.set()         ## 设置为True,wait 可以走了
 7         time.sleep(5)
 8         print("BOSS:<22:00>可以下班了。")
 9         print(event.isSet())  ## False
10         event.set()   ## 设置为True,wait后面可以走了
11 class Worker(threading.Thread):
12     def run(self):
13         event.wait()
14         print("Worker:哎……命苦啊!")
15         time.sleep(1)
16         event.clear()   ## 清除标识,也就是改为False,wait不能走了
17         event.wait()
18         print("Worker:OhYeah!")
19 if __name__=="__main__":
20     event=threading.Event()     ## 同步对象
21     threads=[]
22     for i in range(5):
23         threads.append(Worker())
24     threads.append(Boss())
25     for t in threads:
26         t.start()
27     for t in threads:
28         t.join()
同步时间event 

 

  • 十、同步信号量(Semaphore)

同步信号量可以控制同时可以有几个线程去执行,本质上也是一把锁

 1 import threading,time
 2 class Mythread(threading.Thread):
 3     def run(self):
 4         Semaphore.acquire()
 5         print(self.name)
 6         time.sleep(3)
 7         Semaphore.release()
 8 
 9 if __name__ == '__main__':
10     t_list = []
11     Semaphore = threading.Semaphore(5)      ##  同时允许五个线程
12     for i in range(100):
13         t = Mythread()
14         t.start()
15         t_list.append(t)
16     for j in t_list:
17         j.join()
18     print('ending...')
信号量 Semaphore

 

  • 十一、线程队列------多线程利器

我们来看一下两个线程来删除一个列表的值时会出现什么样的情况

 1 import threading,time
 2 
 3 li=[1,2,3,4,5]
 4 
 5 def pri():
 6     while li:
 7         a=li[-1]
 8         print(a)
 9         time.sleep(1)
10         try:
11             li.remove(a)
12         except Exception as e:
13             print('----',a,e)
14 
15 t1=threading.Thread(target=pri,args=())
16 t1.start()
17 t2=threading.Thread(target=pri,args=())
18 t2.start()
19 # 5
20 # 5
21 # 4
22 # ---- 5 list.remove(x): x not in list
23 # 4
24 # 3
25 # ---- 4 list.remove(x): x not in list
26 # 3
27 # 2
28 # ---- 3 list.remove(x): x not in list
29 # 2
30 # 1
31 # ---- 2 list.remove(x): x not in list
32 # 1
33 # ---- 1 list.remove(x): x not in list
列表

可以看得出来,列表是不安全的数据结构,当两个线程同时共享同一个列表时,列表可能会出错

所以在线程中,用队列相比列表安全得多了

1、队列

队列也是一种数据结构,效果和列表一样,只适用于线程和进程中

queue队列类的方法

 1 创建一个“队列”对象
 2 import Queue
 3 q = Queue.Queue(maxsize = 10)
 4 Queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。
 5 
 6 将一个值放入队列中
 7 q.put(10)
 8 调用队列对象的put()方法在队尾插入一个项目。put()有两个参数,第一个item为必需的,为插入项目的值;第二个block为可选参数,默认为
 9 1。如果队列当前为空且block为1,put()方法就使调用线程暂停,直到空出一个数据单元。如果block为0,put方法将引发Full异常。
10 
11 将一个值从队列中取出
12 q.get()
13 调用队列对象的get()方法从队头删除并返回一个项目。可选参数为block,默认为True。如果队列为空且block为True,
14 get()就使调用线程暂停,直至有项目可用。如果队列为空且block为False,队列将引发Empty异常。
15 
16 Python Queue模块有三种队列及构造函数:
17 1、Python Queue模块的FIFO队列先进先出。   class queue.Queue(maxsize)
18 2、LIFO类似于堆,即先进后出。               class queue.LifoQueue(maxsize)
19 3、还有一种是优先级队列级别越低越先出来。        class queue.PriorityQueue(maxsize)
20 
21 此包中的常用方法(q = Queue.Queue()):
22 q.qsize() 返回队列的大小
23 q.empty() 如果队列为空,返回True,反之False
24 q.full() 如果队列满了,返回True,反之False
25 q.full 与 maxsize 大小对应
26 q.get([block[, timeout]]) 获取队列,timeout等待时间
27 q.get_nowait() 相当q.get(False)
28 非阻塞 q.put(item) 写入队列,timeout等待时间
29 q.put_nowait(item) 相当q.put(item, False)
30 q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号
31 q.join() 实际上意味着等到队列为空,再执行别的操作
queue队列类的方法 

2、三种队列模式:

(1)先进先出FIFO(first in first out)(默认)

 1 import queue
 2 q = queue.Queue(3)   ##  创建一个线程队列,参数3代表最多能放3个队列
 3 q.put(21)
 4 q.put('hello')
 5 q.put({'name':'alex'})
 6 # q.put((12,23),False)        ##  put(self, item, block=True, timeout=None)   这里block默认值为True,如果满了则不能塞数据,必须要有数据被取了才能往里塞,队列会卡在这里。如果block设置为False,则满了再塞就报错
 7 while True:
 8     data = q.get()          ##  get(self, block=True, timeout=None)  这里block默认值为True,如果空了就等待,直到有数据被塞入,如果设置为False则空了就报错
 9     print(data)         
10 # 21
11 # hello
12 # {'name': 'alex'}
FIFO

(2)先进后出LIFO(later in first out)

 1 import queue
 2 q = queue.LifoQueue(3)
 3 
 4 q.put(123)
 5 q.put('hello')
 6 q.put({'name':'alex'})
 7 
 8 while 1:
 9     date = q.get(block=False)
10     print(date)
11     print('------------------')
12 # {'name': 'alex'}
13 # ------------------
14 # hello
15 # ------------------
16 # 123
17 # ------------------
LIFO

(3)优先级

 1 import queue
 2 q = queue.PriorityQueue()
 3 q.put([2,23])       ##  第一个元素是优先级,数字越小优先级越高
 4 q.put([4,'hello'])
 5 q.put([3,{'name':'alex'}])
 6 while True:
 7     data = q.get()
 8     print(data)
 9 # [2, 23]
10 # [3, {'name': 'alex'}]
11 # [4, 'hello']
优先级

3、生产者消费者模型

为什么要使用生产者消费者模型?

在线程世界中,生产者就是生产数据的线程,消费者就是消费数据的线程。如果生产者生产数据很快,消费者消费数据很慢,那么生产者就需要等待消费者处理完,才能继续产生数据;反之也一样,消费者就必须要等待生产者生产出数据,才能继续处理。

什么是生产者消费者模型?

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。而队列就是两者之间的容器,也就是中介。

这就像,在餐厅,厨师做好菜,不需要直接和客户交流,而是交给前台,而客户去饭菜也不需要不找厨师,直接去前台领取即可,这也是一个结耦的过程。

 1 import threading,queue,random,time
 2 
 3 q = queue.Queue()
 4 
 5 def Produce(name):
 6     count = 1
 7     while count < 4:
 8         print('开始制作包子...')
 9         time.sleep(random.randrange(3))
10         q.put(count)
11         print('Produce %s 制作好了第%s个包子...'%(name,count))
12         count += 1
13         print('ok')
14 
15 def Consumer(name):
16     count = 1
17     while count<4:
18         time.sleep(random.randrange(4))
19         if not q.empty():
20             date = q.get()
21             print(date)
22             print('\033[32;1mConsumer%s吃了第%s个包子...\033[0m'%(name,date))
23             count += 1
24         else:
25             print('----没有包子了----')
26 
27 
28 p1 = threading.Thread(target=Produce,args=('A厨师',))
29 c1 = threading.Thread(target=Consumer,args=('B消费者',))
30 p1.start()
31 c1.start()
32 # 开始制作包子...
33 # ----没有包子了----
34 # Produce A厨师 制作好了第1个包子...
35 # ok
36 # 开始制作包子...
37 # 1
38 # ConsumerB消费者吃了第1个包子...
39 # Produce A厨师 制作好了第2个包子...
40 # ok
41 # 开始制作包子...
42 # Produce A厨师 制作好了第3个包子...
43 # ok
44 # 2
45 # ConsumerB消费者吃了第2个包子...
46 # 3
47 # ConsumerB消费者吃了第3个包子...
生产者消费者模型

 

  • 十二、进程

1、进程的调用方式

(1)直接调用

 1 import multiprocessing,time
 2 def movie():
 3     print('begin to movie ',time.ctime())
 4     time.sleep(1)
 5     print('stop to movie ',time.ctime())
 6 
 7 if __name__ == '__main__':
 8     p1 = multiprocessing.Process(target=movie)
 9     p2 = multiprocessing.Process(target=movie)
10     p3 = multiprocessing.Process(target=movie)
11     p1.start()
12     p2.start()
13     p3.start()
14     p1.join()
15     p2.join()
16     p3.join()
17     print('ending......')
18     # begin to movie  Sun Apr 12 14:12:23 2020
19     # begin to movie  Sun Apr 12 14:12:23 2020
20     # begin to movie  Sun Apr 12 14:12:23 2020
21     # stop to movie  Sun Apr 12 14:12:24 2020
22     # stop to movie  Sun Apr 12 14:12:24 2020
23     # stop to movie  Sun Apr 12 14:12:24 2020
24     # ending......
直接调用

(2)继承调用

 1 import multiprocessing,time
 2 class Myprocess(multiprocessing.Process):
 3     def run(self):
 4         print(self.name,'begin to run',time.ctime())
 5         time.sleep(3)
 6         print(self.name,'stop to run',time.ctime())
 7 
 8 if __name__ == '__main__':
 9     p_list = []
10     for p in range(3):
11         p = Myprocess()
12         p.start()
13         p_list.append(p)
14     for j in p_list:
15         j.join()
16     print('ending....')
17     
18     # Myprocess-1 begin to run Sun Apr 12 14:24:00 2020
19     # Myprocess-2 begin to run Sun Apr 12 14:24:00 2020
20     # Myprocess-3 begin to run Sun Apr 12 14:24:00 2020
21     # Myprocess-1 stop to run Sun Apr 12 14:24:03 2020
22     # Myprocess-2 stop to run Sun Apr 12 14:24:03 2020
23     # Myprocess-3 stop to run Sun Apr 12 14:24:03 2020
24     # ending....
继承调用

 

2、daemon方法(守护进程)

 1 import multiprocessing,time
 2 class Myprocess(multiprocessing.Process):
 3     def run(self):
 4         print(self.name,'begin to run',time.ctime())
 5         time.sleep(3)
 6         print(self.name,'stop to run',time.ctime())
 7 
 8 if __name__ == '__main__':
 9     for p in range(3):
10         p = Myprocess()
11         p.daemon = True     ##  这里deamon是实例属性
12         p.start()
13 
14     print('ending....')
15     # ending....
守护进程

  

  3、进程与子进程的pid

 1 import time,os
 2 from multiprocessing import Process
 3 
 4 def info(name):
 5     time.sleep(1)
 6     print('name:',name)
 7     print('parent procces:',os.getppid())       ##      父进程
 8     print('pid id',os.getpid())
 9 
10 
11 
12 if __name__ == '__main__':
13     ##  主进程
14     info('alex')
15     print('------------------')
16 
17     ##  子进程
18     p = Process(target=info,args=('yuan',))
19     p.start()
20     p.join()
21 
22     print('end')
23     # name: alex
24     # parent procces: 3404      ------------->  pycharm 的进程号
25     # pid id 1120               ------------->  主进程pid
26     # ------------------
27     # name: yuan
28     # parent procces: 1120      
29     # pid id 7868               ------------->  子进程的pid
30     # end
进程pid

 

4、Process类

构造方法:

Process([group [, target [, name [, args [, kwargs]]]]])

  group: 线程组,目前还没有实现,库引用中提示必须是None; 
  target: 要执行的方法; 
  name: 进程名; 
  args/kwargs: 要传入方法的参数。

实例方法:

  is_alive():返回进程是否在运行。

  join([timeout]):阻塞当前上下文环境的进程程,直到调用此方法的进程终止或到达指定的timeout(可选参数)。

  start():进程准备就绪,等待CPU调度

  run():strat()调用run方法,如果实例进程时未制定传入target,这star执行t默认run()方法。

  terminate():不管任务是否完成,立即停止工作进程

属性:

  daemon:和线程的setDeamon功能一样

  name:进程名字。

  pid:进程号。

 

  • 十三、进程间的通信

(1)进程队列Queue

import multiprocessing,time
def run(q):
    time.sleep(1)
    q.put(123)
    q.put('hello')
    q.put({'name':'alex'})
    
if __name__ == '__main__':
    q = multiprocessing.Queue() ##  创建进程队列对象
    p = multiprocessing.Process(target=run,args=(q,))
    p.start()
    for i in range(3):
        print(q.get())

    print('ending....')
    # 123
    # hello
    # {'name': 'alex'}
    # ending....
进程队列

注意:进程队列和线程队列是不一样的,线程队列的数据是共享的,而进程队列不是,例如上面的例子,子进程把数据放到队列里,实际上是拷贝了一份数据给父进程,这也就是为什么进程间通信很耗资源。

(2)管道通信Pipe

 1 import multiprocessing,time
 2 def son(conn):
 3     print('子进程收到的数据是',conn.recv())
 4     conn.send({'name':'alex'})
 5     print('子进程的Pipe ID是:',id(conn))
 6     conn.close()
 7     
 8 if __name__ == '__main__':
 9     parent_conn,son_conn = multiprocessing.Pipe()       ##  创建双向管道对象
10     p = multiprocessing.Process(target=son,args=(son_conn,))
11     p.start()
12     parent_conn.send('hello son')
13     print('主进程收到的数据是:',parent_conn.recv())
14     print('主进程的Pipe ID是:',id(parent_conn))
15     
16     # 子进程收到的数据是 hello son
17     # 子进程的Pipe ID是: 44101248
18     # 主进程收到的数据是: {'name': 'alex'}
19     # 主进程的Pipe ID是: 39919288
双管道通信Pipe

管道通信也是资源拷贝的过程,并不是数据共享

(3)Manager(数据共享)

上面两个都不能做到数据共享,只有Manager可以做到数据共享

types listdictNamespaceLockRLockSemaphoreBoundedSemaphoreConditionEventBarrierQueueValue and Array. 这些都可以共享

 1 from multiprocessing import Manager,Process
 2 def son(num,d,l):
 3     d[num] = '2'
 4     d['2'] = 2
 5     l.append(num)
 6 
 7 if __name__ == '__main__':
 8 
 9     with Manager() as manager:         ##  创建方式
10         d = manager.dict()     ##  创建一个Manager类字典
11         l = manager.list(range(5))     ##  创建一个Manager类列表
12         p_list = []
13         for i in range(10):
14             p = Process(target=son,args=(i,d,l))
15             p.start()
16             p_list.append(p)
17         for j in p_list:
18             j.join()
19         print('共享之后的字典是:',d)
20         print('共享之后的列表是:',l)
21         # 共享之后的字典是: {3: '2', '2': 2, 2: '2', 1: '2', 0: '2', 5: '2', 6: '2', 4: '2', 7: '2', 9: '2', 8: '2'}
22         # 共享之后的列表是: [0, 1, 2, 3, 4, 3, 2, 1, 0, 5, 6, 4, 7, 9, 8]
Manager

 

 

  • 十四、进程同步锁
 1 from multiprocessing import Process,Lock
 2 import time
 3 
 4 def foo(l,num):
 5 
 6     with l:
 7         time.sleep(0.5)
 8     ##  l.acquire()
 9         print('hello %d'%num)
10     ##  l.release()
11 if __name__ == '__main__':
12 
13     lock = Lock()
14 
15     for num in range(10):
16 
17         p = Process(target=foo,args=(lock,num,))
18         p.start()
未加锁

运行结果:

 可以看出来,虽然进程之间数据是独立的,但是还是会共享资源,如屏幕等等,上面就出现了抢占资源的情况,所以还是要加一把同步锁防止乱序

有两种方式加锁:

(1)with lock:

(2)lock.acquire()

   lock.release()

 1 from multiprocessing import Process,Lock
 2 import time
 3 
 4 def foo(l,num):
 5 
 6     with l:     ##  第一种方式
 7         time.sleep(0.5)
 8      # l.acquire()        ##  第二种方式
 9         print('hello %d'%num)
10      # l.release()
11 if __name__ == '__main__':
12 
13     lock = Lock()
14 
15     for num in range(10):
16 
17         p = Process(target=foo,args=(lock,num,))
18         p.start()
同步锁

 

 

  • 十五、进程池

模拟一个场景:有一百块砖需要搬,假如搬一块砖需要1秒钟,最坏的情况是一个人去搬,搬一百秒,但是效率太低了。最理想的情况是一百个人去搬,1秒搬完,这样子开销又太大了

        现在就用一个折中的办法,开一个池子(容量是五),五个人去搬,搬走一块池子就加一块,这样20秒就能搬完,这就是进程池的概念

 1 from multiprocessing import Pool
 2 import time
 3 def foo(num):
 4     time.sleep(1)
 5     print('进程%s运行'%num)
 6 
 7 if __name__ == '__main__':
 8     pool = Pool(5)      ##  运行同时5个进程运行
 9     for i in range(100):
10         # p = pool.apply(func=foo,args=(i,))        ##  同步接口,只允许一个进程走
11         p = pool.apply_async(func=foo,args=(i,))
12     pool.close()        ##  这两个顺序是固定的
13     pool.join()
进程池

回调函数

每次执行子进程都会调用一次回调函数,回调函数是在主进程中运行,必须一个参数,接收的参数是子进程的返回值,通常用在logger中

 1 from multiprocessing import Pool
 2 import time,os
 3 def foo(num):
 4     time.sleep(1)
 5     print('进程%s运行'%num)
 6     print('子进程的pid:',os.getpid())
 7     return 'hello %s'%num
 8 
 9 def back(arg):
10     print('back:',os.getpid())
11     print(arg)
12 
13     
14 if __name__ == '__main__':
15     print('主进程的pid:',os.getpid())
16     pool = Pool(5)
17     for i in range(100):
18         p = pool.apply_async(func=foo,args=(i,),callback=back)      ## callback 回调函数
19                                                                     ##  每次执行一个子进程,就会调用一次pid ,回调函数是在主进程运行
20 
21     pool.close()
22     pool.join()
回调函数

 

 

十六、协程(重点)

1、生成器

可以调用__next__()或者next()方法的就是一个生成器,生成器函数有一个重要的方法,yield

def foo():
    print('第一次运行')
    yield 2
    print('第二次运行')
    y = yield 3
    print(y)
    
f = foo()
print(f)        ##  <generator object foo at 0x00000000021155E8>    生成器对象
生成器

yield有两种调用方法:next 和 send

 1 def foo():
 2     print('第一次运行')
 3     yield 2     ##  第一次保持状态     ##  第一次next走到这里
 4     print('第二次运行')
 5     y = yield 3    ##   第二次保持状态     ##  第二次next走到这里
 6     print(y)
 7 
 8 f = foo()
 9 print(f)        ##  <generator object foo at 0x00000000021155E8>    生成器对象
10 print(next(f))         ##  第一次运行    2
11 f.__next__()            ##  第二次运行
next调用
 1 def foo():
 2     print('第一次运行')
 3     s = yield 2     ##  第一次保持状态     ##  第一次send走到这里
 4     print('s的值是',s)
 5     print('第二次运行')
 6     y = yield 3    ##   第二次保持状态     ##  第二次send走到这里
 7     print(y)
 8 
 9 f = foo()
10 next(f)     ##  第一次运行
11 f.send(5)       ##  注意,这里send是将5传给上一次保持的状态的值
12 # 第一次运行
13 # s的值是 5
14 # 第二次运行
send调用

 

2、协程-----------非抢占式程序

协程,又称微线程,纤程。英文名Coroutine。协程本质上就是一个线程

优点1: 协程极高的执行效率,没有切换的消耗。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

优点2: 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

 1 import time
 2 
 3 def Custommer(name):
 4     print('ready to eat baozi...')
 5     while 1:
 6         new_baozi = yield
 7         print('[%s] is eating baozi %s'%(name,new_baozi))
 8         time.sleep(1)
 9 
10 def Producer():
11     r = con1.__next__()
12     r = con2.__next__()
13     n = 0
14     while 1:
15         print('produce is making baozi[%s] and baozi[%s]'%(n,n+1))
16         con1.send(n)
17         con2.send(n+1)
18         n += 2
19         time.sleep(1)
20 
21 
22 con1 = Custommer('con1')
23 con2 = Custommer('con2')
24 Producer()
简单的协程

 

3、greenlet模块

 1 from greenlet import greenlet
 2 def test1():
 3     print(12)
 4     gr2.switch()
 5     print(56)
 6 
 7 def test2():
 8     print(34)
 9     gr1.switch()
10     print(78)
11     gr1.switch()
12 
13 gr1 = greenlet(test1)
14 gr2 = greenlet(test2)
15 gr2.switch()
16 # 34
17 # 12
18 # 78
19 # 56
greenlet 

用户根据自己需求切换,切换顺序图:

缺点:用户需要根据每个IO阻塞进行切换,不太现实

 

4、gevent

 1 import gevent
 2 import requests,time
 3 start=time.time()
 4 def f(url):
 5     print('GET: %s' % url)
 6     resp =requests.get(url)
 7     data = resp.text
 8     print('%d bytes received from %s.' % (len(data), url))
 9 
10 # f('https://www.python.org/')      ##  串行
11 # f('https://www.yahoo.com/')
12 # f('https://www.baidu.com/')
13 # f('https://www.sina.com.cn/')
14 # f("http://www.xiaohuar.com/hua/")
15 
16 gevent.joinall([
17         gevent.spawn(f, 'https://www.python.org/'),         ##  协程
18         gevent.spawn(f, 'https://www.yahoo.com/'),
19         gevent.spawn(f, 'https://www.baidu.com/'),
20         gevent.spawn(f, 'https://www.sina.com.cn/'),
21         gevent.spawn(f, 'http://www.xiaohuar.com/hua/'),
22 ])
23 
24 
25 print("cost time:",time.time()-start)
gevent 协程

 

posted @ 2020-04-12 20:55  毛新觉罗  阅读(215)  评论(0编辑  收藏  举报