Python 线程、 进程和协程

 

 

python提供了两个模块来实现多线程thread 和threading ,thread 有一些缺点,在threading 得到了弥补,为了不浪费时间,所以我们直接学习threading 就可以了。

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

复制代码
 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*- 
 3 
 4 import threading
 5 import time
 6 
 7 def show(arg):
 8     time.sleep(1)
 9     print 'thread'+str(arg)
10 
11 for i in range(10):                 #开启10个线程
12     t = threading.Thread(target=show, args=(i,)) #创建线程t,使用threading.Thread()方法,在这个方法中调用show方法target=show,args方法对show进行传参。
13                                                     
14     t.start()
15 print 'main thread stop'
复制代码

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

  python中的多线程,有一个GIL(Global Interpreter Lock 全局解释器锁 )在同一时间只有一个线程在工作,他底层会自动进行上下文切换.这个线程执行点,那个线程执行点!

更多方法:

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

    setName       为线程设置名称

    getName     获取线程名称

    setDaemon     setDaemon(True)将线程声明为前台线程,必须在start() 方法调用之前设置
              如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止
              如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止

     join         逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义    

     run         线程被cpu调度后执行Thread类对象的run方法

线程锁

由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,CPU接着执行其他线程。所以,可能出现抢占屏幕打印问题,出现乱序问题,执行上面的代码可以直接看到效果.所以就出现了线程锁机制,专门解决这个问题.

设置线程锁

python的锁可以独立提取出来

复制代码
#锁的使用
#创建锁
lock= threading.Lock()
#锁定
lock.acquire([timeout])#锁定方法acquire可以有一个超时时间的可选参数timeout。如果设定了timeout,则在超时后通过返回值可以判断是否得到了锁,从而可以进行一些其他的处理。
#释放
lock.release()
复制代码

设置线程锁:

复制代码
 1 #!/usr/bin/env python
 2 #coding:utf-8
 3 
 4 import threading
 5 import time
 6 
 7 gl_num = 0
 8 lock = threading.RLock()        #创建线程锁
 9 
10 def Func():
11     lock.acquire()              #锁定
12     global gl_num
13     gl_num +=1
14     time.sleep(1)
15     print gl_num
16     lock.release()              #释放锁
17 
18 for i in range(10):
19     t = threading.Thread(target=Func)
20 
21     t.start()
复制代码

event

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

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

clear:将“Flag”设置为False

set:  将“Flag”设置为True

复制代码
 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*- 
 3 
 4 import threading
 5 
 6 def do(event):
 7     print 'start'
 8     event.wait() #执行对象wait方法,然后他们停下来,等待“Flag”为True
 9     print 'execute'
10 
11 event_obj = threading.Event()   #创建事件的对象
12 
13 for i in range(10):
14     t = threading.Thread(target=do, args=(event_obj,))  #把方法do传到每个线程里面,
15     t.start()
16 
17 event_obj.clear()   #设置"Flag"为Flase
18 
19 inp = raw_input('input:')
20 if inp == 'true':
21     event_obj.set()
复制代码

 

 multiprocessing是要比fork更高级的库了,使用multiprocessing可以更加轻松的实现多进程程序。

复制代码
#!/usr/bin/env python
# -*- coding:utf-8 -*- 
from multiprocessing import Process
import threading
import time

def foo(i):
    print 'say hi',i

for i in range(10):
    p = Process(target=foo,args=(i,))
    p.start()
复制代码

注意:由于进程之间的数据需要各自持有一份,所以创建进程需要的非常大的开销。并且python不能再Windows下创建进程!

使用多进程的时候,最好是创建和和CPU核数相等的进程数?:

进程数据共享

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

默认的进程之间相互是独立,如果想让进程之间数据共享,就得有个特殊的数据结构,这个数据结构就可以理解为他有穿墙的功能 如果你能穿墙的话两边就都可以使用了

复制代码
#!/usr/bin/env python
#coding:utf-8
 
from multiprocessing import Process
from 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
复制代码

使用特殊的数据类型,来进行穿墙:

复制代码
#通过特殊的数据结构:数组(Array)

from multiprocessing import Process,Array

#创建一个只包含数字类型的数组(python中叫列表)
#并且数组是不可变的,在C,或其他语言中,数组是不可变的,之后再python中数组(列表)是可以变得
#当然其他语言中也提供可变的数组
#在C语言中数组和字符串是一样的,如果定义一个列表,如果可以增加,那么我需要在你内存地址后面再开辟一块空间,那我给你预留多少呢?
#在python中的list可能用链表来做的,我记录了你前面和后面是谁。   列表不是连续的,数组是连续的

'''
上面不是列表是“数组"数组是不可变的,附加内容是为了更好的理解数组!
'''

temp = Array('i', [11,22,33,44]) #这里的i是C语言中的数据结构,通过他来定义你要共享的内容的类型!点进去看~
 
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  #这个特殊的数据类型Manager
 
manage = Manager()
dic = manage.dict() #这里调用的时候,使用字典,这个字典和咱们python使用方法是一样的!
 
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() 
复制代码
1     'c': ctypes.c_char,  'u': ctypes.c_wchar,
2     'b': ctypes.c_byte,  'B': ctypes.c_ubyte,
3     'h': ctypes.c_short, 'H': ctypes.c_ushort,
4     'i': ctypes.c_int,   'I': ctypes.c_uint,
5     'l': ctypes.c_long,  'L': ctypes.c_ulong,
6     'f': ctypes.c_float, 'd': ctypes.c_double

既然进程之间可以进行共享数据,如果多个进程同时修改这个数据是不是就会造成脏数据?是不是就得需要锁!

进程的锁和线程的锁使用方式是非常一样的知识他们是用的类是在不同地方的

1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 from multiprocessing import Process, Array, RLock
 5 
 6 def Foo(lock,temp,i):
 7     """
 8     将第0个数加100
 9     """
10     lock.acquire()
11     temp[0] = 100+i
12     for item in temp:
13         print i,'----->',item
14     lock.release()
15 
16 lock = RLock()
17 temp = Array('i', [11, 22, 33, 44])
18 
19 for i in range(20):
20     p = Process(target=Foo,args=(lock,temp,i,))
21     p.start()
22 
23 进程锁

进程锁
进程锁

进程池

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

进程池中有两个方法:

  • apply
  • apply_async
复制代码
 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 from  multiprocessing import Process,Pool
 4 import time
 5   
 6 def Foo(i):
 7     time.sleep(2)
 8     return i+100
 9   
10 def Bar(arg):
11     print arg
12   
13 pool = Pool(5) #创建一个进程池
14 #print pool.apply(Foo,(1,))#去进程池里去申请一个进程去执行Foo方法
15 #print pool.apply_async(func =Foo, args=(1,)).get()
16   
17 for i in range(10):
18     pool.apply_async(func=Foo, args=(i,),callback=Bar)
19   
20 print 'end'
21 pool.close()
22 pool.join()#进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。
23 
24 '''
25 apply 主动的去执行
26 pool.apply_async(func=Foo, args=(i,),callback=Bar) 相当于异步,当申请一个线程之后,执行FOO方法就不管了,执行完之后就在执行callback ,当你执行完之后,在执行一个方法告诉我执行完了
27 callback 有个函数,这个函数就是操作的Foo函数的返回值!
28 '''
复制代码

 

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

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

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

 greenlet

复制代码
#!/usr/bin/env python
# -*- coding:utf-8 -*- 
 
from greenlet import greenlet
 
def test1():
    print 12
    gr2.switch()#切换到协程2执行
    print 34 #2切回来之后,在这里和yield类似
    gr2.switch() 
 
def test2():
    print 56
    gr1.switch()#上面执行了一句,在切换到协程1里去了
    print 78
 
gr1 = greenlet(test1) #创建了一个协程
gr2 = greenlet(test2)

gr1.switch() #执行test1 

'''
比I/O操作,如果10个I/O,我程序从上往下执行,如果同时发出去了10个I/O操作,那么返回的结果如果同时回来了2个
,是不是就节省了很多时间?

如果一个线程里面I/O操作特别多,使用协程是不是就非常适用了!

如果一个线程访问URL通过协程来做,协程告诉它你去请求吧,然后继续执行,但是如果不用协程就得等待第一个请求完毕之后返回之后才
继续下一个请求。

协程:把一个线程分成了多个协程操作,每个协程做操作
多线程:是把每一个操作,分为多个线程做操作,但是python中,在同一时刻只能有一个线程操作,并且有上下文切换。但是如果上下文切换非常频繁的话
是非常耗时的,但对于协程切换就非常轻便了~
复制代码

协程就是对线程的分片,上面的例子需要手动操作可能用处不是很大了解原理,看下面的例子:

上面的greenlet是需要认为的制定调度顺序的,所以又出了一个gevent他是对greenlet功能进行封装

复制代码
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操作自动切换:

复制代码
 1 from gevent import monkey; monkey.patch_all()
 2 import gevent
 3 import urllib2
 4 
 5 def f(url):
 6     print('GET: %s' % url)
 7     resp = urllib2.urlopen(url) #当遇到I/O操作的时候就会调用协程操作,然后继续往下走,然后这个协程就卡在这里等待数据的返回
 8     data = resp.read()
 9     print('%d bytes received from %s.' % (len(data), url))
10 
11 gevent.joinall([
12         gevent.spawn(f, 'https://www.python.org/'),  #这里的f是调用这个方法,第二个是调用方的参数
13         gevent.spawn(f, 'https://www.yahoo.com/'),
14         gevent.spawn(f, 'https://github.com/'),
15 ]) 
16 
17 '''
18 gevent.spawn(f, 'https://www.python.org/'),  #这里的f是调用这个方法,第二个是调用方的参数
19 
20 当函数f里的代码遇到I/O操作的时候,函数就卡在哪里等待数据的返回,但是协程不会等待而是继续操作!
21 '''
复制代码

*
*

*

参考文档:http://www.cnblogs.com/wupeiqi/articles/5040827.html

     http://www.cnblogs.com/luotianshuai/p/5111587.html

原文地址:https://www.cnblogs.com/onesea/p/15459094.html