python 之线程、进程、协程

一、线程(Thread)
1.什么是线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务
模块名:threading 

更多方法:

  • start       线程准备就绪,等待CPU调度
  • setName     为线程设置名称
  • getName     获取线程名称
  • setDaemon   设置为后台线程或前台线程(默认)
  •             如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止
  •             如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止
  • join        逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义
  • run         线程被cpu调度后执行Thread类对象的run方法

2.线程有2种调用方式,如下:
a).直接调用
    1. #!/usr/bin/env python
    2. # -*-coding:utf-8 -*-
    3. import threading
    4. import time
    5. def test(num):
    6. print("runing num %s" %num)
    7. time.sleep(5)
    8. if __name__ == "__main__":
    9. t1 = threading.Thread(target=test,args=(1,))
    10. #注意这里的test是上面的方法,这里不要加括号,加括号就直接调用了。
    11. #这里args里面的内容是以元组或列表的形式将方法的内容传入进去的(上面的()可以改成[]),这里必须加逗号,若没有传入参数,则直接写成args[]
    12. t2 = threading.Thread(target=test,args=(2,))
    13. t1.start() #启动第一个线程
    14. t2.start() #启动第二个线程
    15. t2.setName("111111111111111") #更改线程名称
    16. print(t1.getName()) #获取线程名
    17. print(t2.getName())
    18. print("The threading done")
    19. #启动序号10-19为一个主线程,而t1.start()、t2.star()为主线程的子线程,也就是说这个时候一共有三个线程
    20. #在主线程后,子线程就并行执行了,而主线程默认是不会等子线程执行完毕的。因此作为主线程中的print("The threading done")会执行完毕,然后等待子线程了。

执行结果:
  1. C:\Python34\python.exe E:/Python/threading_test.py
  2. runing num 1
  3. runing num 2
  4. Thread-1
  5. 111111111111111
  6. The threading done
  7. Process finished with exit code 0


注意:在主线程启动后,子线程被启动,这时候主线程和子线程就会并行执行了。但上面的输出会有些停顿感,是由于主线程已经执行完(if __name__的代码都执行完毕了),而子线程只完成了test()方法的print那一步,后面的time.sleep还没执行完,等待子线程执行完就会输出结果了。(非常重要)

在某个场景下,需要等待子线程执行完,主线程在执行完,可以使用threading 的join 方法
由于子线程是随机的,因此要等待所用子线程执行完毕,可以采用以下方式:
  1. #!/usr/bin/env python
  2. # -*-coding:utf-8 -*-
  3. import threading
  4. import time
  5. def test(num):
  6. print("runing num %s" %num)
  7. time.sleep(5)
  8. if __name__ == "__main__":
  9. # t1 = threading.Thread(target=test,args=(1,)) #注意这里args里面的内容就是test方法的num输入参数,这里必须加逗号
  10. # t2 = threading.Thread(target=test,args=(2,))
  11. # t1.start() #启动第一个线程
  12. # t2.start() #启动第二个线程
  13. test_threading = []
  14. for i in range(10): #启动10个线程
  15. i = threading.Thread(target=test,args=(i,)) #进行实例化
  16. i.start()
  17. test_threading.append(i) #把实例加入列表里面
  18. i.setName("test%s" %i)
  19. print(i.getName())
  20. for i in test_threading: #这一步是确保所有线程都执行完毕后在往下走,假如放入上面的for循环中,就会存在需要等待每个都执行完,这样就变成串行的了。
  21. i.join()
  22. print("The threading done")

b)继承式调用
  1. import threading
  2. import time
  3. class test(threading.Thread):
  4. def __init__(self,num):
  5. threading.Thread.__init__(self) #继承threading.Thread的方法
  6. #super(test,self).__init__() #与上面功能以上,是两种不同的类的集成写法
  7. self.num = num
  8. def info(self):
  9. print("test class %s" %self.num)
  10. time.sleep(5)
  11. if __name__ == "__main__":
  12. t1 = test(1)
  13. t2 = test(2)
  14. t1.start()
  15. t2.start()
  16. print(t2.getName())


3.Daemon  (守护)使用
当把主线程(test)设置为守护线程之后,守护线程执行完成退出后,被他守护的子线程(test启动的子线程)就被迫退出了。
从下面的参数可以看出,test并没有等待run的方法里面的sleep和里面的print执行就退出了。
这个需求,在一种场景下用到,主进程设置守护进程,守护进程退出就全部退出了。例如使用批量命令进行远程ssh命令,部分主机不成功,会等待,未来避免这种情况,由主进程进行判断(加入经验值,等待时间,)
这个脚本的的主线程启动了main,main又启动了5个子线程
  1. import time
  2. import threading
  3. def run(n):
  4. print('[%s]------running----\n' % n)
  5. time.sleep(3)
  6. print("----run done----")
  7. def test():
  8. for i in range(3):
  9. t = threading.Thread(target=run,args=[i,])
  10. t.start()
  11. print('starting thread', t.getName())
  12. if __name__ == "__main__":
  13. m = threading.Thread(target=test,args=[])
  14. m.setDaemon(True) #将主线程设置为Daemon线程,它退出时,其它子线程会同时退出,不管是否执行完任务
  15. m.start()
  16. print("---main thread done---")

threading.activeCount()方法返回当前进程中线程的个数。返回的个数中包含主线程。
threading.enumerate()方法返回当前运行中的Thread对象列表。

  1. import threading
  2. import time
  3. def test(num):
  4. print("runing num %s" %num)
  5. time.sleep(1)
  6. if __name__ == "__main__":
  7. # t1 = threading.Thread(target=test,args=(1,)) #注意这里args里面的内容就是test方法的num输入参数,这里必须加逗号
  8. # t2 = threading.Thread(target=test,args=(2,))
  9. # t1.start() #启动第一个线程
  10. # t2.start() #启动第二个线程
  11. test_threading = []
  12. for i in range(3): #启动10个线程
  13. i = threading.Thread(target=test,args=(i,)) #进行实例化
  14. i.start()
  15. test_threading.append(i) #把实例加入列表里面
  16. i.setName("test%s" %i)
  17. print(i.getName())
  18. print("目前的线程数",threading.active_count())
  19. print(threading.enumerate())
  20. for i in test_threading: #这一步是确保所有线程都执行完毕后在往下走,假如放入上面的for循环中,就会存在需要等待每个都执行完,这样就变成串行的了。
  21. i.join()
  22. print("The threading done")
  23. print("现在的线程数",threading.active_count())

执行结果:
  1. C:\Python34\python.exe E:/Python/threading_test.py
  2. runing num 0
  3. test<Thread(Thread-1, started 8256)>
  4. runing num 1
  5. test<Thread(Thread-2, started 8312)>
  6. runing num 2
  7. test<Thread(Thread-3, started 8324)>
  8. 目前的线程数 4
  9. [<Thread(test<Thread(Thread-1, started 8256)>, started 8256)>, <Thread(test<Thread(Thread-2, started 8312)>, started 8312)>, <Thread(test<Thread(Thread-3, started 8324)>, started 8324)>, <_MainThread(MainThread, started 4788)>]
  10. The threading done
  11. 现在的线程数 1
  12. Process finished with exit code 0

4.python中的多线程,有一个GIL(Global Interpreter Lock 全局解释器锁 )在同一时间只有一个线程在工作,他底层会自动进行上下文切换.
GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。


5.互斥锁、递归锁、信号量
a).互斥锁
互斥锁是为了解决程序在同时调用一个数据时产生的
  1. import time
  2. import threading
  3. def addNum():
  4. global num #在每个线程中都获取这个全局变量
  5. print('--get num:',num )
  6. time.sleep(1)
  7. num -=1 #对此公共变量进行-1操作
  8. num = 100 #设定一个共享变量
  9. thread_list = []
  10. for i in range(100):
  11. t = threading.Thread(target=addNum)
  12. t.start()
  13. thread_list.append(t)
  14. for t in thread_list: #等待所有线程执行完毕
  15. t.join()
  16. print('final num:', num )

一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,正常来讲,这个num结果应该是0, 多运行几次,会发现,最后打印出来的num结果不总是0。主要是由于,由于线程之间是进行随机调度 。假设有A和B两个线程, 会同时拿到相同的数据,交给cpu去运算,当A线程去处完的结果是99,但此时B线程运算完的结果也是99,两个线程同时CPU运算的结果再赋值给num变量后,结果就都是99。为了避免自己在还没改完的时候别人也来修改此数据,可以给这个数据加一把锁, 这样其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。 

*注:不要在3.x上运行,不知为什么,3.x上的结果总是正确的,可能是自动加了锁

不加锁版本:
  1. import time
  2. import threading
  3. def addNum():
  4. global num #在每个线程中都获取这个全局变量
  5. print('--get num:',num )
  6. time.sleep(1)
  7. num -=1 #对此公共变量进行-1操作
  8. num = 100 #设定一个共享变量
  9. thread_list = []
  10. for i in range(100):
  11. t = threading.Thread(target=addNum)
  12. t.start()
  13. thread_list.append(t)
  14. for t in thread_list: #等待所有线程执行完毕
  15. t.join()
  16. print('final num:', num )
加锁后的版本:
  1. import time
  2. import threading
  3. def addNum():
  4. global num #在每个线程中都获取这个全局变量
  5. print('--get num:',num )
  6. time.sleep(1)
  7. lock.acquire() #修改前加锁
  8. num -=1 #对此公共变量进行-1操作
  9. lock.release() #修改后释放
  10. num = 100 #设定一个共享变量
  11. thread_list = []
  12. lock = threading.Lock() #生成全局锁
  13. for i in range(100):
  14. t = threading.Thread(target=addNum)
  15. t.start()
  16. thread_list.append(t)
  17. for t in thread_list: #等待所有线程执行完毕
  18. t.join()
  19. print('final num:', num )
b).RLock(递归锁)  
在一个大锁中还要再包含子锁,这个是保证数据唯一性方法,常用方法。

  1. import threading,time
  2. def run1():
  3. print("grab the first part data")
  4. lock.acquire()
  5. global num
  6. num +=1
  7. lock.release()
  8. return num
  9. def run2():
  10. print("grab the second part data")
  11. lock.acquire()
  12. global num2
  13. num2+=1
  14. lock.release()
  15. return num2
  16. def run3(): #为了确保run1 和run2 同时运行完毕。 这里可以理解RUN3为RUN2和RUN1的总方法。
  17. lock.acquire()
  18. res = run1()
  19. print('--------between run1 and run2-----')
  20. res2 = run2()
  21. lock.release()
  22. print(res,res2)
  23. if __name__ == '__main__':
  24. num,num2 = 0,0
  25. lock = threading.RLock() #设置一个递归锁
  26. #这里若不加Rlock使用lock会出现太多的锁,会进入死循环。
  27. for i in range(10):
  28. t = threading.Thread(target=run3)
  29. t.start()
  30. while threading.active_count() != 1: #现在还有几个线程
  31. print(threading.active_count())
  32. else:
  33. print('----all threads done---')
  34. print(num,num2)

c).Semaphore(信号量)

互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 。比如有10个线程,设置最多允许3个线程允许,后面的线程只能等待前面计算完成后才能运行。(保护资源利用率和程序最优)

  1. #!/user/bin/env python
  2. #-*- coding:utf-8 -*-
  3. author = 'yangrf'
  4. import threading
  5. import time
  6. def test(n):
  7. locks.acquire() #加锁
  8. print("test111")
  9. time.sleep(1)
  10. print("%s" %n)
  11. locks.release() #释放锁
  12. if __name__ == "__main__":
  13. num = 0
  14. locks = threading.BoundedSemaphore(3) #最多同时允许3个进程运行
  15. for i in range(50): #启动10个进程
  16. t = threading.Thread(target=test,args=[i,])
  17. t.start()
  18. print(threading.active_count())
  19. while threading.active_count() != 1:
  20. pass
  21. else:
  22. print('----all threads done---')
  23. print(num)

6.Events

作用:用主线程控制子线程合适执行,可以让子线程停下来,也可以让线程继续!
实现的机制就是:标志位“Flag”

事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。

  • clear:将“Flag”设置为False
  • set:将“Flag”设置为True


    通过Event来实现两个或多个线程间的交互,下面是一个红绿灯的例子,即起动一个线程做交通指挥灯,生成几个线程做车辆,车辆行驶按红灯停,绿灯行的规则。以下为金角的例子。
  1. import threading,time
  2. import random
  3. def light():
  4. if not event.isSet():
  5. event.set() #wait就不阻塞 #绿灯状态
  6. count = 0
  7. while True:
  8. if count < 10:
  9. print('\033[42;1m--green light on---\033[0m')
  10. elif count <13:
  11. print('\033[43;1m--yellow light on---\033[0m')
  12. elif count <20:
  13. if event.isSet():
  14. event.clear()
  15. print('\033[41;1m--red light on---\033[0m')
  16. else:
  17. count = 0
  18. event.set() #打开绿灯
  19. time.sleep(1)
  20. count +=1
  21. def car(n):
  22. while 1:
  23. time.sleep(random.randrange(10))
  24. if event.isSet(): #绿灯
  25. print("car [%s] is running.." % n)
  26. else:
  27. print("car [%s] is waiting for the red light.." %n)
  28. if __name__ == '__main__':
  29. event = threading.Event()
  30. Light = threading.Thread(target=light)
  31. Light.start()
  32. for i in range(3):
  33. t = threading.Thread(target=car,args=(i,))
  34. t.start()

7.queue队列 
天生的线程的安全
class queue.Queue(maxsize=0) #先入先出
class queue.LifoQueue(maxsize=0) #last in fisrt out   先进后出,或最后的先出
class queue.PriorityQueue(maxsize=0) #存储数据时可设置优先级的队列 ,使用元组进行表达,只要做了优先级,put 都需要做优先级设置。

优先级必须以元组的形成进行写,优先级越小越高
可以放置列表、元组、字典、类等数据
#!/user/bin/env python
#-*- coding:utf-8 -*-
import queue
##########没有数据,就阻塞了################
# q = queue.Queue(maxsize=3) #设置Q的大小
# q.get(timeout=3)
 
 
q = queue.PriorityQueue(maxsize=30)
q.put((5,[123])) #元组中第一个数字为优先级
q.put((2,"test"))
q.put((6,{1,"111",2,"33333"}))
q.put((3,33333333333333333333333),timeout=3)
q.put((4,456),timeout=3)
#print(q.full())
#q.get(timeout=3)
print(q.get_nowait())
print(q.get_nowait())
 
 
#get_nowait() 是无论里面有没有数据,都不等待,而get会等待,若没有数据就直接阻塞了
#print(q.full())
#print(q.empty())


q的几个方法:
exception queue.Empty  判断Q是否为空
queue.Full  判断是否为Q满了
Queue.qsize()  设置Q的长度
Queue.get(block=Truetimeout=None)  #设置超时,当Q满了就会报异常,若不写则Q满了,就一致等
Queue.put_nowait(item)  #不等,如果Q满了,就直接抛出异常。
Queue.task_done() 起到信号作用,在生产者消费者模型使用

8.生产者消费者模型

主要保持供需平衡
来自互联网:生产者和消费者之间用中间类似一个队列一样的中间媒介,这里定义为“仓库”。对于生产者只需要关心这个“仓库”,并不需要关心具体的消费者,对于生产者而言甚至都不知道有这些消费者存在。对于消费者而言他也不需要关心具体的生产者,到底有多少生产者也不是他关心的事情,他只要关心这个“仓库”中还有没有东西。只要仓库没有东西了生产者就生产,只要仓库有东西消费者就能获取。就可以实现点对点和广播两种方式进行消息的分发。
在python里实现生产者消费者模型主要是使用queue.Queue( )的 task_done( ) 和 join( )方法互动来实现。
  1. import queue,threading,time
  2. q = queue.Queue()
  3. def consumer(n):
  4. while True:
  5. print("\033[32;1mconsumer [%s]\033[0m get task: %s" %(n,q.get()))
  6. time.sleep(1)
  7. q.task_done() #通知队列获取了一个内容,队列减一
  8. def producer(n):
  9. count = 1
  10. while True:
  11. time.sleep(1)
  12. if q.qsize() <3 : #如果小于3,产量就不够了,就开始生产
  13. print("producer [%s] produced a new task: %s" %(n,count))
  14. q.put(count)
  15. count += 1
  16. q.join() #queue is emtpy #这里就开始阻塞了,直到为空,线程在这里就挂起了,不会占CPU资源,等待这个emtpy才会执行
  17. print("all tasks has been cosumed by consumers...")
  18. c1 = threading.Thread(target=consumer,args=[1,])
  19. c2 = threading.Thread(target=consumer,args=[2,])
  20. c3 = threading.Thread(target=consumer,args=[3,])
  21. p1 = threading.Thread(target=producer,args=["test",])
  22. p2 = threading.Thread(target=producer,args=["soft",])
  23. c1.start()
  24. c2.start()
  25. c3.start()
  26. p1.start()
  27. p2.start()






二、进程
IO密集型的使用。可以充分使用CPU的多核特性。
简单的例子:
  1. #!/user/bin/env python
  2. #-*- coding:utf-8 -*-
  3. from multiprocessing import Process #导入进程模块
  4. import time
  5. def f(name):
  6. time.sleep(2)
  7. print('hello',name)
  8. if __name__ == "__main__":
  9. p = Process(target=f,args=['test',]) #启动一个进程
  10. p.start() #启动进程
  11. p.join()

1.可以通过以下方法获取子进程的ID 

  1. from multiprocessing import Process
  2. import os
  3. def info(title):
  4. print(title)
  5. print('module name:', __name__)
  6. print('主进程ID', os.getppid()) #打印父进程ID
  7. print('本程序进程(子进程)ID:', os.getpid()) #打印子线程ID
  8. print("\n\n")
  9. def f(name):
  10. info('\033[31;1mfunction f\033[0m')
  11. print('hello', name)
  12. if __name__ == '__main__':
  13. info('\033[32;1mmain process line\033[0m')
  14. #这个时候打印的父进程ID是运行这个程序的ID(使用pycharm,这个运行就是pycharm的ID),子进程是这个程序的主ID
  15. p = Process(target=info, args=('test',))
  16. #这个时候打印的父进程ID为这个主程序的ID,子进程为启动的这个子进程的ID
  17. p.start()
  18. p.join()

执行结果:
  1. C:\Python34\python.exe E:/Python/multiprocess_test.py
  2. main process line
  3. module name: __main__
  4. 主进程ID 7912
  5. 本程序进程(子进程)ID: 11444
  6. test
  7. module name: __mp_main__
  8. 主进程ID 11444
  9. 本程序进程(子进程)ID: 9368
  10. Process finished with exit code 0

由于本程序是使用pycharm运行的,因此  7912  为pycharm的ID,在任务管理器查看情况如下:


2.进程间通讯  

不同进程间内存是不共享的,要想实现两个进程间的数据交换,可以用以下方法:

a).Queues

使用方法跟threading里的queue差不多

Queues 有两个方法put 和get

进程通过Queues模块实现进程间的通信。Q的好处是线程安全的。无论有多少的进程去Q中放数据还是取数据都能保持数据的一致性。同时间只处理一个数据,自带加锁,Q通常用于多线程中。

这里使用的不是通常的Q,是在进程中做了一个封装,实现了子进程和父进程都能访问这个Q

  1. from multiprocessing import Process,Queue
  2. def f(q): #把Q传进去
  3. q.put([1111,2222])
  4. #这里必须传进去,否则子进程无法取到数据
  5. if __name__ == '__main__':
  6. q = Queue()
  7. p1 = Process(target=f, args=(q,))
  8. p2 = Process(target=f, args=(q,))
  9. p1.start()
  10. p2.start()
  11. print(q.get()) # prints "[42, None, 'hello']"
  12. print(q.qsize())
  13. p1.join()

注:以上的方式:当启动多进程时候,取第一个数据具体是什么是不知道的,因为子进程执行顺序是不定的。

b).Pipes 管道功能,两头取数据。

管道一头赋给父进程,另一个赋给子进程,两者通过管道取数据。

可以实现一对多。实现进程间的数据传递

  1. from multiprocessing import Process, Pipe
  2. def f(conn):
  3. conn.send([42, None, 'hello'])
  4. #子进程send一个数据
  5. conn.close()
  6. #把管道关闭
  7. if __name__ == '__main__':
  8. parent_conn, child_conn = Pipe() #生成一个管道
  9. p = Process(target=f, args=(child_conn,))
  10. p.start()
  11. print(parent_conn.recv()) # 父进程来接收
  12. p.join()

c).Managers 进程间的共享,同一个数据,能被多个进程修改

 

可以支持 

listdictNamespaceLockRLockSemaphoreBoundedSemaphoreConditionEventBarrierQueueValue and Array等同时被多个进程访问。

  1. from multiprocessing import Process,Manager
  2. def test(d,l,n): #同时传入了两个类型数据
  3. d[1] = "test1"
  4. d[2] = "test2"
  5. d[3] = "test3"
  6. l.append(n)
  7. print(l)
  8. if __name__ == "__main__":
  9. #with Manager() as manager
  10. d = Manager().dict() #通过Manger生成一个dict,这是必须的
  11. l = Manager().list(range(10)) #通过Manger生成一个list,放入10个值
  12. print("111111111111111111",l)
  13. p_list = []
  14. for i in range(10):
  15. p = Process(target=test, args=(d, l,"list_%s" %i))
  16. p.start()
  17. p_list.append(p)
  18. for res in p_list:
  19. res.join()
  20. print(d)
  21. print(l)
执行结果:
  1. C:\Python34\python.exe E:/Python/manager_test.py
  2. 111111111111111111 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  3. [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'list_2']
  4. [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'list_2', 'list_0']
  5. [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'list_2', 'list_0', 'list_4']
  6. [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'list_2', 'list_0', 'list_4', 'list_6']
  7. [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'list_2', 'list_0', 'list_4', 'list_6', 'list_3']
  8. [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'list_2', 'list_0', 'list_4', 'list_6', 'list_3', 'list_1']
  9. [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'list_2', 'list_0', 'list_4', 'list_6', 'list_3', 'list_1', 'list_7']
  10. [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'list_2', 'list_0', 'list_4', 'list_6', 'list_3', 'list_1', 'list_7', 'list_9']
  11. [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'list_2', 'list_0', 'list_4', 'list_6', 'list_3', 'list_1', 'list_7', 'list_9', 'list_8']
  12. [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'list_2', 'list_0', 'list_4', 'list_6', 'list_3', 'list_1', 'list_7', 'list_9', 'list_8', 'list_5']
  13. #通过输出结果发现,每个数据是被共享的
  14. {1: 'test1', 2: 'test2', 3: 'test3'}
  15. [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'list_2', 'list_0', 'list_4', 'list_6', 'list_3', 'list_1', 'list_7', 'list_9', 'list_8', 'list_5']
  16. Process finished with exit code 0

3.进程同步,为了保障数据的唯一性。加锁(这个问题3.0已经接近,在2.7存在乱写数据的问题)

  1. from multiprocessing import Process, Lock
  2. def f(l, i):
  3. l.acquire() #加锁
  4. try:
  5. print('hello world', i)
  6. finally:
  7. l.release() #去锁
  8. if __name__ == '__main__':
  9. lock = RLock() #设置一个锁
  10. for num in range(100):
  11. Process(target=f, args=(lock, num)).start()

4.进程池 

进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。

进程池中有两个方法:

  • apply
  • apply_async

限制进程数

  1. from multiprocessing import Process,Pool,freeze_support
  2. import time
  3. def Foo(i):
  4. time.sleep(2)
  5. return i+100
  6. def test(arg):
  7. print('-->exec done:',arg)
  8. if __name__ == '__main__':
  9. freeze_support()  
  10.  #windows 无法产生一个新的进程,需要加入freeze_support()
  11. pool = Pool(3) #允许最大5个进程运行
  12.  for i in range(10):#生成10个进程
  13.      pool.apply_async(func=Foo, args=(i,),callback=test)
  14. #pool.apply(func=Foo, args=(i,))
  15. #callback 为回调,执行这个进程完成后要执行的动作
  16. #使用callback ,Foo的return,会在test里面打印出来。
  17. print('The list end')
  18. pool.close()
  19. pool.join()#进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。

三、协程

协程,又称微线程,纤程。英文名Coroutine。协程是一种用户态的轻量级线程

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:

协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。

协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。


适用场景:其实在其他语言中,协程的其实是意义不大的多线程即可已解决I/O的问题,但是在python因为他有GIL(Global Interpreter Lock 全局解释器锁 )在同一时间只有一个线程在工作,所以:如果一个线程里面I/O操作特别多,协程就比较适用

 协程的好处:

  • 无需线程上下文切换的开销
  • 无需原子操作锁定及同步的开销
  • 方便切换控制流,简化编程模型
  • 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。

缺点:

  • 无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
  • 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

1.greentet

  1. from greenlet import greenlet
  2. def test1():
  3. print (12)
  4. gr2.switch()#切换到协程2执行
  5. #这里的gr2在下面已经定义了
  6. print (34) #2切回来之后,在这里和yield类似
  7. gr2.switch()
  8. def test2():
  9. print (56)
  10. gr1.switch()#上面执行了一句,在切换到协程1里去了
  11. print (78)
  12. gr1 = greenlet(test1) #创建了一个协程
  13. gr2 = greenlet(test2)
  14. gr1.switch() #执行test1


2.Gevent 

Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

遇到IO进行自动切换,不等待进行其他协程,实现了上面例子的手动设置效果。

  1. import gevent
  2. def foo():
  3. print('Running in foo')
  4. gevent.sleep(1)
  5. print('Explicit context switch to foo again')
  6. def bar():
  7. print('Explicit context to bar')
  8. gevent.sleep(1)
  9. print('Implicit context switch back to bar')
  10. gevent.joinall([
  11. gevent.spawn(foo), #启动一个协程
  12. gevent.spawn(bar), #启动一个协程
  13. ])
以上效果是,当启动foo协程时候,遇到sleep就会切换到bar去执行,然后再切换到foo,就实现并行效果不阻塞了,这个切换过程是随机的

另一个例子,

遇到IO阻塞时会自动切换任务


  1. from gevent import monkey; monkey.patch_all()
  2. import gevent
  3. from urllib.request import urlopen
  4. def f(url):
  5. print('GET: %s' % url)
  6. resp = urlopen(url) #输入一个url
  7. data = resp.read() #把结果读下来
  8. print('%d bytes received from %s.' % (len(data), url)) #获取的字节数
  9. gevent.joinall([
  10. gevent.spawn(f, 'https://www.python.org/'),
  11. gevent.spawn(f, 'https://www.baidu.com/'),
  12. gevent.spawn(f, 'https://github.com/'),
  13. ])

通过gevent实现单线程下的多socket并发

服务器端:
  1. import sys
  2. import socket
  3. import time
  4. import gevent
  5. from gevent import socket,monkey

  6. monkey.patch_all() #
  7. def server(port):
  8. s = socket.socket()
  9. s.bind(('0.0.0.0', port))
  10. s.listen(500)
  11. while True:
  12. cli, addr = s.accept()
  13. gevent.spawn(handle_request, cli) #启动一个新的协程,把客户端对象传入近期。
  14. def handle_request(s): #s 是每个客户端对象
  15. try:
  16. while True:
  17. data = s.recv(1024)
  18. print("recv:", data)
  19. s.send(data)
  20. if not data:
  21. s.shutdown(socket.SHUT_WR)
  22. except Exception as ex:
  23. print(ex)
  24. finally:
  25. s.close()
  26. if __name__ == '__main__':
  27. server(8001)
客户端:
  1. import socket
  2. HOST = 'localhost' # The remote host
  3. PORT = 8001 # The same port as used by the server
  4. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  5. s.connect((HOST, PORT))
  6. while True:
  7. msg = bytes(input(">>:"),encoding="utf8")
  8. s.sendall(msg)
  9. data = s.recv(1024)
  10. #print(data)
  11. print('Received', repr(data))
  12. s.close()



















posted @ 2016-03-16 23:47  worter  阅读(311)  评论(0编辑  收藏  举报