python的进程和线程

线程和进程


概念

什么是进程:进程就是一个过程,一个任务。进程和线程,都是一个任务而已。。。

什么是并行和并发:CPU同一时刻只能执行一个任务。 无论是并行还是并发,都是让用户看来是在同时运行,多进程就是CPU快速的在不同的进程间来回切换。

  1. 并行:并行就是多个CPU同时运行多个任务(每个CPU运行一个任务),或者单个CPU包含多个核心。 有多少个CPU或多少个核心,就可以同时执行多少个任务。
  2. 并发:伪并行,是指CPU在多道程序间来回切换,实现的多个程序同时运行的效果。

同步与异步的概念:

  1. 同步:就是当一个进程在执行一个请求时,如果需要等待一段时间得到返回信息时,那么该进程一直等待,直到收到返回消息后才继续执行。
  2. 异步:就是当一个进程在执行一个请求时,不管其他进程的状态,不等待返回结果继续向下执行,当有消息返回时系统会发出通知,此时再进行处理。很大的提升了效率

进程的创建: 用户创建的进程,都是由系统来负责的。无论采用什么方式创建的进程,都是调用操作系统的接口来创建的,进程的切换都是由操作系统控制的。 无论是哪一种创建进程的方式,新的进程创建,都是由一个已经存在的进程执行了一个用于创建进程的系统调用得到的。

父进程和子进程:子进程创建后和父进程都有各自独立的地址空间,进程是通过多道技术使每个进程在物理层面上隔离的。任何一个进程在其地址空间的休息都不会影响到另外一个进程。 注意:子进程和父进程之间是可以有只读的共享的内存区域的。 进程与进程之间数据(资源)是隔离的,两个进程之间可以基于管道这种方式进行通信。在Unix当中,是含有进程层次的概念的,但是在windows当中,是没有进程层次的概念的,所有的进程都是地位相同的。 在Linux当中,每启动一个命令,都会启动一个进程。

什么是线程:一个进程里面至少有一个控制线程,进程的概念只是一种抽象的概念,真正在CPU上面调度的是进程 里面的线程,就像是铁路系统一样,铁路系统内至少有一条铁路线(线程),铁路线是跑火车的(实际工作的),火车共享着铁路系统提供的资源(使用铁路的设施)。也就是说,进程是一个抽象的概念,更像是一个容器,里面包含很多干活的线程,这些线程共享着同一个进程里包含的所有资源,而线程是一个调度单位,不包含资源。

什么时候需要开启多个线程:一个进程里面的多个线程共享这个进程里面的资源,因此如果多个任务共享同一块资源的时候,需要开启多个线程。多线程指的是,在一个进程中开启多个线程。 简单的说:如果多个任务共用同一个资源空间,那么必须在一个进程内开启多个线程。

多线程和多进程的应用方向:

  1. 对于计算密集型应用,应该使用多进程;
  2. 对于IO密集型应用,应该使用多线程。 线程的创建比进程的创建开销小的多。

Python中线程的特点: 在其他语言当中,一个进程里面开启多个线程,每个线程都可以给一个cpu去使用,但是在python当中,在同一时刻,一个进程当中只能有一个线程处于运行状态。 eg:在其他语言当中,比如我现在开启了一个进程,这个进程当中含有几个线程,如果我现在有多个cpu,每一个线程是可以对应相应的CPU的。 但是在python当中,如果我们现在开启了一个进程,这个进程里面对应多个线程,同一时刻只有一个线程可以处于运行状态。对于其他语言而言,在多CPU系统中,为了最大限度的利用多核,可以开启多个线程。 但是Python中的多线程是利用不了多核优势的。

进程与线程的区别:

在同一个进程当中,多个线程彼此之间可以相互通信;但是进程与进程之间的通信必须基于IPC这种消息的通信机制(IPC机制包括队列和管道)。 在一个进程当中,改变主线程可能会影响其它线程的行为,但是改变父进程并不会影响其它子进程的行为,因为进程与进程之间是完全隔离的。 在python当中,在同一时刻同一进程当中只能同时有一个线程在运行,如果有一个线程使用了系统调用而阻塞,那么整个进程都会被挂起。

进程举例

开启子进程

 1 #方法一:
 2 from multiprocessing import Process
 3 import os
 4 import time
 5 
 6 
 7 def task(msg):
 8     print('process:%s PID:%s PPID:%s ' % (msg, os.getpid(), os.getppid()))
 9 
10 
11 if __name__ == '__main__':
12     for i in range(3):
13         p = Process(target=task, args=('进程%s' % i,))  #创建进程对象
14         p.start()   #开启子进程
15 
16     print('')
 1 #方法二:
 2 from multiprocessing import Process
 3 import os
 4 
 5 
 6 class MyProcess(Process):   #使用类的方法开启子进程,需要继承Process模块
 7     def __init__(self, msg):
 8         super().__init__()   #从用父类的__init__方法
 9         self.msg = msg
10 
11     def run(self):   #必须使用run这个名字,固定模式。
12         print('process:%s PID:%s PPID:%s ' % (self.msg, os.getpid(), os.getppid()))
13 
14 
15 if __name__ == '__main__':
16     for i in range(3):
17         p = MyProcess('进程%s' % i)
18         p.start()

守护进程

 1 from multiprocessing import Process
 2 import time
 3 import os
 4 
 5 
 6 def task(msg):
 7     print('this is daemon %s PID:%s start' % (msg, os.getpid()))
 8     time.sleep(2)   #模拟运行时间
 9     print('daemon %s PID:%s is done.' % (msg, os.getpid()))
10 
11 
12 if __name__ == '__main__':
13     p = Process(target=task, args=('守护',))
14     p.daemon = True   #设置为守护进程
15     p.start()
16     time.sleep(1)  模拟主进程运行时间,当1秒过后主进程结束守护进程一起死掉。
17     print('')
18 #当主进程的运行时间超过守护进程的时间,就可以看到子进程运行结束的信息输出了。

互斥锁

 1 from multiprocessing import Process, Lock
 2 import os
 3 import time
 4 
 5 
 6 def task(name, mutex):
 7     mutex.acquire() #开启互斥锁
 8     print('the process %s pid:%s' % (name, os.getpid()))
 9     time.sleep(1)
10     mutex.release() #释放互斥锁
11 
12 
13 if __name__ == '__main__':
14     mutex = Lock() #创建互斥锁
15     for i in range(3):
16         p = Process(target=task, args=('子进程%s' % i, mutex,)) #进程互斥锁需要作为参数带入
17         p.start()
18     print('')
19 #进程谁先抢到互斥锁谁就先执行,当执行完毕释放互斥锁后其他进程才能再得到互斥锁来执行。

互斥锁和JOIN的区别:

  1. 互斥锁:对需要共享数据的局部代码进行串行执行
  2. join:对程序整体进行串行

进程Queue

进程Queue,即队列,可以存放任意类型的数据。是用来在多个进程间通讯使用的。队列不宜存放大数据,适合存放精简的小数据。队列占用的是内存空间,应该适当设置队列大小。

通过生产者消费者模型可以很容易的理解队列的作用(解耦合),但使用MultiProcess的队列,会使程序都在一个主机上运行,这就形成了集中式,影响稳定性,性能局限(不便于拓展)

 1 #生产者消费者模型 
 2 from multiprocessing import Process, Queue
 3 import time
 4 
 5 
 6 def producer(q):    #Q为队列对象
 7     for i in range(5):
 8         res = '产品%s' % i   #生产的的内容 
 9         time.sleep(1)
10         print('生产了%s' % res)
11         q.put(res)     #将生产的内容放入队列
12 
13 
14 def consumer(q):  
15     while True: #使用while是因为不位置生产的个数。
16         res = q.get()   #获得队列中的内容
17         if res is None:  #判断队列是否为空,空就结束当前进程。
18             break
19         time.sleep(1.5)
20         print("获得了%s" % res)
21 
22 
23 if __name__ == '__main__':
24     q = Queue()  #创建队列
25     p1 = Process(target=producer, args=(q,))
26     p2 = Process(target=producer, args=(q,))
27     p3 = Process(target=producer, args=(q,))
28     c1 = Process(target=consumer, args=(q,))
29     c2 = Process(target=consumer, args=(q,))
30     #以上是3个生产者进程,和2个消费者进程
31     p1.start()
32     p2.start()
33     p3.start()
34     c1.start()
35     c2.start()
36     #同时开启5个进程
37     p1.join()
38     p2.join()
39     p3.join()
40     #等待3个生产进程全部结束
41     q.put(None)
42     q.put(None)
43     #有几个消费者就要有几个PUT(NONE)。给消费者提供结束信号。
44 
45     print('主进程')

JoinableQueue 用法

 1 #生产者消费者模型
 2 from multiprocessing import Process, JoinableQueue
 3 import time
 4 
 5 
 6 def producer(q):
 7     for i in range(5):
 8         res = '产品%s' % i
 9         time.sleep(1)
10         print('生产了%s' % res)
11         q.put(res)
12     q.join()            #要在生产者中加入JOIN(),等待消费者发出信号。
13 
14 def consumer(q):
15     while True:
16         res = q.get()
17         if res is None:
18             break
19         time.sleep(1.5)
20         print("获得了%s" % res)
21         q.task_done()   #发出信号给join()告知已经取完队列中所有信息
22 
23 if __name__ == '__main__':
24     q = JoinableQueue()
25     p1 = Process(target=producer, args=(q,))
26     p2 = Process(target=producer, args=(q,))
27     p3 = Process(target=producer, args=(q,))
28     c1 = Process(target=consumer, args=(q,))
29     c2 = Process(target=consumer, args=(q,))
30 
31     c1.daemon = True        #守护进程
32     c2.daemon = True
33 
34     p1.start()
35     p2.start()
36     p3.start()
37     c1.start()
38     c2.start()
39 
40     p1.join()
41     p2.join()
42     p3.join()  #当所有生产者运行完毕后,主进程结束,同时消费者作为守护进程也结束。
43 
44     print('主进程')

开启线程

开启线程与开启进程是一样的方法。

 1 #方法一:
 2 from threading import Thread
 3 import os
 4 import time
 5 
 6 
 7 def task(msg):
 8     print('Thread:%s PID:%s PPID:%s ' % (msg, os.getpid(), os.getppid()))
 9 
10 
11 if __name__ == '__main__':
12     for i in range(3):
13         p = Thread(target=task, args=('线程%s' % i,))  #创建线程对象
14         p.start()   #开启线程
15 
16     print('')
 1 #方法二:
 2 from threading import Thread
 3 import os
 4 
 5 
 6 class MyThread(Thread):   #使用类的方法开启线程,需要继承Thread模块
 7     def __init__(self, msg):
 8         super().__init__()   #从用父类的__init__方法
 9         self.msg = msg
10 
11     def run(self):   #必须使用run这个名字,固定模式。
12         print('Thread:%s PID:%s PPID:%s ' % (self.msg, os.getpid(), os.getppid()))
13 
14 
15 if __name__ == '__main__':
16     for i in range(3):
17         p = MyThread('线程%s' % i)
18         p.start()

线程对象中的方法:

currentThread() 返回当前线程的变量

currentThread().getName() 获取当前线程的名字

setName() 设置线程名

enumerate() 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。

active_count() 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

守护线程

 1 from threading import Thread
 2 import time
 3 
 4 
 5 def t1():
 6     print('线程开启111')
 7     time.sleep(1)
 8     print('线程1111结束')
 9 
10 
11 def t2():
12     print('线程开启2222')
13     time.sleep(2)
14     print('线程2222结束')
15 
16 
17 if __name__ == '__main__':
18     t1 = Thread(target=t1)
19     t2 = Thread(target=t2)
20     t1.daemon = True
21     t1.start()
22     t2.start()
23     print('主线程')

和守护进程不一样,由于T2还没有运行结束,主线程等待所有线程结束后才能结束,这时T1线程由于比T2线程运行时间短,所以T1可以正常执行结束。(这与守护进程是不一样的。归根结底就是,进程是独立的,线程是共享同一进程的,所以守护进程和守护线程的不同之处就在这里)

线程互斥锁

 1 from threading import Thread, Lock
 2 import time
 3 
 4 n =100
 5 
 6 def task():
 7     global n
 8     mutex.acquire()
 9     time.sleep(0.1)  #这里可以不用睡0.1秒。因为线程是秒开的不需要开系统资源,但在CPYthon解释器
10                      #以外的其他解释下执行,就需要加入等待时间了。因为CPYthon的GIL垃圾回收特性
11                      #使得,同一个进程下的多个线程在同一时间只能运行一个,无法发挥计算机的多核优
12                      #势,但在其他python解释下则不然。
13     n -= 1
14     mutex.release()
15 
16 
17 if __name__ == '__main__':
18     mutex = Lock()   #定义互斥锁
19     ls = []    
20 
21     for i in range(100):
22         t1 = Thread(target=task, name='%s' % i) #name是设置了线程名
23         t1.start()
24         ls.append(t1)
25         print(t1)
26 
27     for i in ls:  #利用循环和列表,对所有开启的线程进行JOIN的等待操作。
28         i.join()
29 
30     print('', n)

线程的互斥锁和进程的互斥锁并没有什么区别,主要的区别就在于,开线程是在同一个进程下开启的,线程会共用进程中的资源(进程是资源单位,线程是运行单位),而进程是系统通过调用接口来创建的,每个进程都会占用一定的系统资源,并且每个进程都是独立的。

GIL锁

GIL是CPython的特性,GIL是解释器层面的一把锁,保证了在同一时间只能有一个活跃的线程来运行PYTHON的字节码(python解释器代码),因为PYthon的内存垃圾回收机制不是安全的,要保证线程的数据安全,就需要用到这个GIL锁,并且PYthon的一些其他功能都会依赖于这把锁。

GIL锁是解释器层面的锁,针对的是垃圾回收功能,它不能保证数据的安全,所以要在GIL的基础上再加上LOCK(互斥锁)确保PYTHON在同一时间只执行一个任务的代码,来保证数据安全。

注:在CPYthon解释器中,同一个进程下开启的多个线程,同一时间只能有一个线程执行,无法利用多核优势!

GIL与多线程

  1. 对于计算来说,CPU越多越好,但是对于I/O来说,再多的CPU也没用。
  2. 如果是单核CPU,程序用线程设计,因为对于单核CPU来说,多进程没有意义。
  3. 如果是多核CPU,面对计算密集型来说要用到多进程,这样计算会很快。(金融类)
  4. 如果是多核CPU,面对I/O密集型来说,要用到多线程,因为多进程与多线程就、效率几乎一样。(网络类)
os.cpu_count()方法 查看计算机CPU核心数

死锁与递归锁

 1 #死锁模拟
 2 from threading import Thread, Lock
 3 import time
 4 
 5 mutexA = Lock()
 6 mutexB = Lock()
 7 
 8 
 9 class MyThread(Thread):  #通过类的方式开启线程
10 
11     def run(self):  #run方法名为固定格式
12         self.f1()
13         self.f2()
14 
15     def f1(self):
16         mutexA.acquire()
17         print('%s 拿到A锁' % self.name)
18         mutexB.acquire()
19         print('%s 拿到B锁' % self.name)
20 
21         mutexB.release()
22         mutexA.release()
23 
24     def f2(self):
25         mutexB.acquire()
26         print('%s 拿到B锁' % self.name)
27         time.sleep(0.1)  #设置0.1秒的等待以便其他线程都启动起来。
28         mutexA.acquire()
29         print('%s 拿到A锁' % self.name)
30 
31         mutexA.release()
32         mutexB.release()
33 
34 
35 if __name__ == '__main__':
36     for i in range(10):
37         t = MyThread()
38         t.start()

这段代码,由于线程1在F2中拿到B锁,但无法拿到A锁,因为线程1在F2中拿到B锁后,睡了0.1秒,这时线程2在F1中拿到了A锁,当线程1,0.1秒时间过后,再去拿A锁,这时A锁已经被线程2占用,而线程2在F1中想拿B锁,可B锁被线程1占用,这时就形成了死锁。

 1 #递归锁
 2 from threading import Thread, RLock
 3 import time
 4 
 5 mutexA = mutexB = RLock()    #A和B都等于递归锁
 6 
 7 
 8 class MyThread(Thread):   #继承Thread类
 9 
10     def run(self):
11         self.f1()
12         self.f2()
13 
14     def f1(self):
15         mutexA.acquire()
16         print('%s 拿到A锁' % self.name)
17         mutexB.acquire()
18         print('%s 拿到B锁' % self.name)
19 
20         mutexB.release()
21         mutexA.release()
22 
23     def f2(self):
24         mutexB.acquire()
25         print('%s 拿到B锁' % self.name)
26         time.sleep(0.1)
27         mutexA.acquire()
28         print('%s 拿到A锁' % self.name)
29 
30         mutexA.release()
31         mutexB.release()
32 
33 
34 if __name__ == '__main__':      #只要递归锁不为0,其他线程就不能执行。
35     for i in range(10):
36         t = MyThread()
37         t.start()

递归锁是将多把锁连接在一起,就像钥匙串一样,当一个线程拿到钥匙串中的任意一把锁或多把锁时,其他的线程都不能拿到该钥匙串中的任意锁,直到该线程将所有的锁释放掉后才可以被其他的线程拿到递归锁。(没得到一把锁就在队列中加一,释放一把锁就字啊队列中减一,直到全部释放为止)

信号量

 1 from threading import Thread, Semaphore, current_thread
 2 import time
 3 
 4 sm = Semaphore(3)
 5 
 6 
 7 def task():
 8     # sm.acquire()
 9     # print('%s in sm' % current_thread().name)
10     # time.sleep(3)
11     # sm.release()
12     with sm:                                #另一种写法。。适用于互斥锁
13         print('%s in sm' % current_thread().getName())
14         time.sleep(3)
15 
16 
17 if __name__ == '__main__':
18     for i in range(10):
19         t = Thread(target=task)
20         t.start()

信号量就是允许多少个线程同时运行,限制同时运行的线程数。

Event事件

 1 from threading import Thread, Event
 2 import time
 3 
 4 event = Event()   #定义EVENT对象。
 5 
 6 
 7 def student(name):
 8     print('%s 正在上课。' % name)
 9     event.wait()        #event.wait()等待SET被执行后执行下面语句
10     event.wait(2)        #如果设置了时间,那么不等SET发出信号,只要到了时间后就会继续执行。
11     print('%s 课间休息.' % name)
12 
13 
14 def teacher(name):
15     print('%s 老师正在上课。。' % name)
16     time.sleep(5)
17     print('%s 老师下课..' % name)
18     time.sleep(0.5)
19     event.set()    #执行set后,wait解除等待,继续执行后面的代码。
20 
21 
22 if __name__ == '__main__':
23     stu1 = Thread(target=student, args=('sly',))
24     stu2 = Thread(target=student, args=('sj',))
25     stu3 = Thread(target=student, args=('lby',))
26     t1 = Thread(target=teacher, args=('good',))
27 
28     stu1.start()
29     stu2.start()
30     stu3.start()
31     t1.start()

Event事件,就是在遇到WAIT进行等待,直到SET被执行后,执行WAIT后的代码。

event.isSet() 返回event的状态值。

event.wait() 如果event.isSet()==False 时将阻塞线程。

event.set() 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态,等待操作系统调度。

event.clear() 恢复event的状态值为False

定时器

 1 #定时器验证码刷新认证:
 2 
 3 from threading import Timer
 4 import random
 5 import string
 6 
 7 class Test(object):
 8     def __init__(self):
 9         self.refresh()   #实例化时给出第一个验证码
10 
11     def refresh(self, time=5):  #刷新时间设置为默认参数
12         self.res = self.get_code()
13         print(self.res)
14         self.t = Timer(time, self.refresh)  #使用定时器,等待time的时间后执行refresh方法。
15         self.t.start()   #启动定时器
16 
17 
18 
19     def get_code(self):   #通过random类得到随机验证码
20         code = ''.join(random.sample(string.digits + string.ascii_letters, 5))
21         return code
22 
23     def check(self):
24         while True:
25             code = input('>>>').strip()
26             if code == self.res:
27                 print('验证码正确!')
28                 self.t.cancel()    #cancel方法 是取消的意思。
29                 break
30 
31 g = Test()
32 g.check()

定时器本身就是一个线程,所以与开线程一样来开定时器。

 1 #小例子看定时器的线程ID和线程名
 2 from threading import Timer, currentThread
 3 import os
 4 
 5 
 6 def refresh(time=2):
 7     print(msg(), currentThread().getName())
 8     t = Timer(time, refresh)
 9     t.start()
10 
11 def msg():
12     code = os.getpid()
13     return code
14 
15 if __name__ == '__main__':
16     refresh()

通过这个小例子可以看到。PID都是同样的并且通过currentThread().getName()得到线程的名字。

线程Queue

 1 import queue   #调用方法
 2 
 3 q = queue.Queue(3)  #设置队列的容量
 4 """先进先出,队列"""
 5 q.put('first')
 6 q.put('second')
 7 q.put('third')
 8 # q.put('hh', block=True, timeout=2)  #默认block是为True阻塞状态, timeout是等待时间
 9 
10 print(q.get())
11 print(q.get())
12 print(q.get())
13 # q.get(block=True, timeout=2) #默认block是为True阻塞状态, timeout是等待时间
14 # q.get_nowait() #与block=False的效果是一样的。不等待。
 1 q = queue.LifoQueue(3)  #设置堆栈的容量
 2 """后进先出,堆栈"""
 3 q.put('first')
 4 q.put('second')
 5 q.put('third')
 6 
 7 print(q.get())
 8 print(q.get())
 9 print(q.get())
10 #最先放入的数据最后被拿出来,最后放进的数据最先被拿出来。
 1 q = queue.PriorityQueue(3)  #设置优先级的容量
 2 """优先级队列"""
 3 q.put((10, 'first'))     #格式为元组的形式。前面的元素是优先级数字,越小优先级越高。
 4 q.put((20, 'second'))
 5 q.put((15, 'third'))
 6 
 7 print(q.get())
 8 print(q.get())
 9 print(q.get())
10 #元组或列表形式存储,数字越小优先级越高,取值时正常。

 1 #进程池和线程池(concurrent.futures模块)
 2 from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
 3 import time
 4 import random
 5 import os
 6 from threading import currentThread
 7 
 8 def task():
 9     print('%s PID:%s' % (currentThread().getName(), os.getpid()))
10     time.sleep(random.randint(1,3))
11 
12 if __name__ == '__main__':
13     # pool = ProcessPoolExecutor(3)
14     pool = ThreadPoolExecutor(3)  #进程池和线程池开启的方式一样
15 
16     for i in range(10):
17         pool.submit(task)   #使用submit异步提交的方式提交到池中,由池进行开启
18 
19     pool.shutdown()   #使用shutdonw来关闭池的入口,池内线程执行完毕后结束。
20 
21     print('主线程', os.getpid())

池与信号量的区别

池的概念与信号量相似,都是允许同一时间有固定多个线程或进程同时执行。但池限定的数量不仅仅是同时执行的个数,而且是开启的线程数也是固定的。而信号量,虽然也是可以控制同时执行的个数,但信号量不会重复使用已开线程,而是开启新的线程。(长时间执行浪费系统资源)

异步调用与回调机制

 1 from concurrent.futures import ThreadPoolExecutor
 2 import time
 3 import random
 4 
 5 
 6 def pt(name):
 7     print('产出中。。。。。。')
 8     time.sleep(random.randint(1,3))
 9     res = random.randint(1, 10)
10     result = {'name': name, 'res': res}
11     print('%s 输出: %s 个' % (name, res))
12     return result
13 
14 def js(pt):
15     pt = pt.result()   #必须使用result()来获得对象的返回值
16     sum = int(pt['res']) * 27
17     print(pt['name'], '获得:', sum)
18 
19 
20 if __name__ == '__main__':
21     pool = ThreadPoolExecutor(5)
22 
23     pool.submit(pt, 'alex').add_done_callback(js)  #回调方法将pool.submit(pt, 'alex')有返回值时(是一个对象),
24                                                    #将这个对象作为 参数 传给 add_done_callback()中的函数。
25     pool.submit(pt, 'sly').add_done_callback(js)   #注意:回调函数执行后得到的是FUTURES对象。
26     pool.submit(pt, 'cool').add_done_callback(js)
27     pool.shutdown()

异步回调与线程并发的区别在于,SUBMIT执行后得到的结果(对象)立即作为adddonecallback()调用函数的参数被执行,然后得到返回结果。而线程并发是多线程一同执行一个函数后,再一同执行关联的函数。

协程

在单一线程下使用程序实现并发。用以欺骗CPU,从而为程序占用最大的CPU资源来提升程序的执行效率。

  • 优点:1,写成的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级。2,单线程内就可以实现并发的效果,最大的限度地利用CPU。
  • 缺点:1,协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程。2,协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程。

协程特点

  1. 必须在只有一个单线程里实现并发。
  2. 修改共享数据不需加锁
  3. 用户程序里自己保存多个控制流的上下文栈
  4. 附加:一个协程遇到I/O操作自动切换到其他协程(如何实现检测I/O,yield,greenlet都无法实现,就用到了gevent模块(select机制))

协程--greenlet模块

 1 #通过pip3 install greenlet 来安装模块
 2 from greenlet import greenlet
 3 
 4 
 5 def eat(name):
 6     print('%s eat 1' % name)
 7     g2.switch('sly')
 8     print('%s eat 2' % name)
 9     g2.switch('sly')  #再次调用可以不用加入参数g2.switch()即可。
10 
11 def play(name):
12     print('%s play 1' % name)
13     g1.switch('sly') #再次调用可以不用加入参数g1.switch()即可。
14     print('%s play 2' % name)
15 
16 
17 g1 = greenlet(eat)
18 g2 = greenlet(play)
19 
20 g1.switch('sly')  #首次调用的时候要加入参数,再次调用的时候可以不用加入参数。

该模块仅仅比yield好一点点,的确实现了程序间的来回切换,但无法检测阻塞。

协程--gevent模块

 1 #使用pip3 install gevent 安装模块
 2 from gevent import monkey  #gevent模块下的monkey类
 3 monkey.patch_all()          
 4 #调用monkey类下的patch_all()方法来检测代码中的阻塞,所以,一定要在最前面进行调用。
 5 #from gevent import monkey;monkey.patch_all()  可以写成这样的格式。
 6 import gevent
 7 import time
 8 
 9 def eat(name):
10     print('%s eat 1' % name)
11     time.sleep(3)
12     print('%s eat 2' % name)
13 
14 
15 def play(name):
16     print('%s play 1' % name)
17     time.sleep(2)
18     print('%s play 2' % name)
19 
20 
21 def read(name):
22     print('%s read 1' % name)
23     time.sleep(4)
24     print('%s read 2' % name)
25 
26 
27 g1 = gevent.spawn(eat, 'sly')  #使用gevent.spawn创建协程对象来提交任务。
28 g2 = gevent.spawn(play, 'lby')
29 g3 = gevent.spawn(read, 'good')
30 
31 # g1.join()
32 # g2.join()
33 # g3.join()
34 
35 gevent.joinall([g1,g2,g3])  #可以用JOINALL这样写。

gevent模块提交的方式为异步提交方式。

协程--基于gevent模块的单线程并发

server 端

 1 from gevent import spawn, monkey; monkey.patch_all()
 2 import socket
 3 
 4 
 5 def communicate(conn):
 6     """负责通信(相当于服务员,对指定的客人服务)"""
 7     while True:
 8         try:
 9             res = conn.recv(1024)
10             if not res:
11                 break
12             conn.send(res.upper())
13         except ConnectionResetError:
14             break           #这里必须是break否则报错。。
15 
16     conn.close()
17 
18 
19 def server(ip, port):
20     server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
21     server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
22     server.bind((ip, port))
23     server.listen(5)
24     print('listen......')
25     while True:
26         conn, client = server.accept()   
27         #等待连接,有连接连入后执行下面代码,没有连接的话等待连接连入。(相当于接待员)
28         print('client IP:%s PORT:%s connection....' % (client[0], client[1]))
29         spawn(communicate, conn)
30     server.close()
31 
32 
33 if __name__ == '__main__':
34     g = spawn(server, '127.0.0.1', 8927)
35     g.join()

client 端

 1 import socket
 2 from threading import Thread, currentThread
 3 
 4 
 5 def client():
 6     client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 7     client.connect(('127.0.0.1', 8927))
 8 
 9     while True:
10         client.send(('%s hello' % currentThread().name).encode('utf-8'))
11         res = client.recv(1024)
12         print(res.decode('utf-8'))
13     client.close()
14 
15 
16 if __name__ == '__main__':
17     for i in range(500):
18         t = Thread(target=client)
19         t.start()

猜你喜欢

转载自www.cnblogs.com/sly27/p/9107707.html