python 线程、进程和协程

来源:互联网 发布:英雄岛全套源码下载 编辑:程序博客网 时间:2024/04/29 19:49

一:线程基础:

线程与进程的不同之处在于,它们共享状态、内存和资源。对于线程来说,这个简单的区别既是它的优势,又是它的缺点。一方面,线程是轻量级的,并且相互之间易于通信,但另一方面,它们也带来了包括死锁、争用条件和高复杂性在内的各种问题。幸运的是,由于 GIL 和队列模块,与采用其他的语言相比,采用 Python 语言在线程实现的复杂性上要低得多。

1:线程分为5种状态


Threading用于提供线程相关的操作,线程是应用程序中工作的最小单元。

#!/usr/bin/env python# -*- coding:utf-8 -*-import threadingimport time  def show(arg):    time.sleep(1)    print('thread'+str(arg))  for i in range(10):    t = threading.Thread(target=show, args=(i,))    t.start()  print('main thread stop')

上述代码创建了10个“前台”线程,然后控制器就交给了CPU,CPU根据指定算法进行调度,分片执行指令。

更多方法:

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

2:线程锁

多线程的优势在于可以同时运行多个任务(至少感觉起来是这样)。但是当线程需要共享数据时,可能存在数据不同步的问题。考虑这样一种情况:一个列表里所有元素都是0,线程"set"从后向前把所有元素改成1,而线程"print"负责从前往后读取列表并打印。那么,可能线程"set"开始改的时候,线程"print"便来打印列表了,输出就成了一半0一半1,这就是数据的不同步。为了避免这种情况,引入了锁的概念。

锁有两种状态——锁定和未锁定。每当一个线程比如"set"要访问共享数据时,必须先获得锁定;如果已经有别的线程比如"print"获得锁定了,那么就让线程"set"暂停,也就是同步阻塞;等到线程"print"访问完毕,释放锁以后,再让线程"set"继续。经过这样的处理,打印列表时要么全部输出0,要么全部输出1,不会再出现一半0一半1的尴尬场面。


由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,CPU接着执行其他线程。所以,可能出现如下问题:

1.1:未使用锁

#!/usr/bin/env python# -*- coding:utf-8 -*-import threadingimport timegl_num = 0def show(arg):    global gl_num    time.sleep(1)    gl_num +=1    print(gl_num)for i in range(10):    t = threading.Thread(target=show, args=(i,))    t.start()print('main thread stop')
使用锁

#!/usr/bin/env python#coding:utf-8import threadingimport timegl_num = 0lock = threading.RLock()   def Func():    lock.acquire()    global gl_num    gl_num +=1    time.sleep(1)    print(gl_num)    lock.release()for i in range(10):    t = threading.Thread(target=Func)    t.start()
二:进程

from multiprocessing import Processimport threadingimport time  def foo(i):    print('say hi',i)  for i in range(10):    p = Process(target=foo,args=(i,))    p.start()
由于进程之间的数据需要各自持有一份,所以创建进程需要的非常大的开销。

2.1:进程数据共享

进程各自持有一份数据,默认无法共享数据

默认无法共享的例子

#!/usr/bin/env python#coding:utf-8 from multiprocessing import Processfrom multiprocessing import Manager import time li = [] def foo(i):    li.append(i)    print('say hi',li)  for i in range(10):    p = Process(target=foo,args=(i,))    p.start()     print('ending',li)
进程共享

#方法一,Arrayfrom multiprocessing import Process,Arraytemp = Array('i', [11,22,33,44]) def Foo(i):    temp[i] = 100+i    for item in temp:        print(i,'----->',item) for i in range(2):    p = Process(target=Foo,args=(i,))    p.start() #方法二:manage.dict()共享数据from multiprocessing import Process,Manager manage = Manager()dic = manage.dict() def Foo(i):    dic[i] = 100+i    print(dic.values()) for i in range(2):    p = Process(target=Foo,args=(i,))    p.start()    p.join()
当创建进程时(非使用时),共享数据会被拿到子进程中,当进程中执行完毕后,再赋值给原值。

进程锁例

#!/usr/bin/env python# -*- coding:utf-8 -*-from multiprocessing import Process, Array, RLockdef Foo(lock,temp,i):    """    将第0个数加100    """    lock.acquire()    temp[0] = 100+i    for item in temp:        print(i,'----->',item)    lock.release()lock = RLock()temp = Array('i', [11, 22, 33, 44])for i in range(20):    p = Process(target=Foo,args=(lock,temp,i,))    p.start()
2.2:进程池

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

进程池中有两个方法:

  • apply
  • apply_async

#!/usr/bin/env python# -*- coding:utf-8 -*-from  multiprocessing import Process,Poolimport time  def Foo(i):    time.sleep(2)    return i+100  def Bar(arg):    print(arg)  pool = Pool(5)#print pool.apply(Foo,(1,))#print pool.apply_async(func =Foo, args=(1,)).get()  for i in range(10):    pool.apply_async(func=Foo, args=(i,),callback=Bar)  print('end')pool.close()pool.join()#进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。

三:协程

线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作则是程序员。

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

协程的适用场景:当程序中存在大量不需要CPU的操作时(IO),适用于协程;

greenlet

#!/usr/bin/env python# -*- coding:utf-8 -*-  from greenlet import greenlet  def test1():    print(12)    gr2.switch()    print(34)    gr2.switch()  def test2():    print(56)    gr1.switch()    print(78) gr1 = greenlet(test1)gr2 = greenlet(test2)gr1.switch()
3.1:gevent

import gevent def foo():    print('Running in foo')    gevent.sleep(0)    print('Explicit context switch to foo again') def bar():    print('Explicit context to bar')    gevent.sleep(0)    print('Implicit context switch back to bar') gevent.joinall([    gevent.spawn(foo),    gevent.spawn(bar),])

遇到IO操作自动切换:

from gevent import monkey; monkey.patch_all()import geventimport urllib2def f(url):    print('GET: %s' % url)    resp = urllib2.urlopen(url)    data = resp.read()    print('%d bytes received from %s.' % (len(data), url))gevent.joinall([        gevent.spawn(f, 'https://www.python.org/'),        gevent.spawn(f, 'https://www.yahoo.com/'),        gevent.spawn(f, 'https://github.com/'),])








1 0
原创粉丝点击