线程、进程和协程

概念

多线程和多进程的区别:
    进程之间不共享内存,线程之间可以共享内存。多进程可以利用多颗cpu,多线程不行。
cpu全局解释器锁(GIL):
    在每个进程的出口,多个线程任务请求cpu调度,只有一个线程能够穿过,所以单个进程不管有多少线程只能调度一个cpu。
多线程、多进程如何选择:
    计算密集型应用:能够利用多颗cpu的性能,采用多进程;

    IO密集型应用:大量的IO操作,调度一颗cpu足以,采用多线程。

主线程:

   程序从上往下执行,解释器的执行过程,叫主线程。

多线程

一个简单的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
import threading
import time
 
lock=threading.Lock()       #加锁防争抢屏幕输出
def run(num):
    lock.acquire()
print "thread......" ,num
    lock.release()
    time.sleep(1)
 
for i in range(10):
    t = threading.Thread(target=run,args=(i,))  #实例化,i后面必须有逗号,每创建一个线程,执行run函数。相当于把i当做num传给run().
    t.start()   #执行start方法
     上述代码创建了10个“前台”线程,然后控制器就交给了CPU,CPU根据指定算法进行调度,分片执行指令。
更多方法:
start     线程准备就绪,等待CPU调度  
setName 为线程设置名称  
getName       获取线程名称  
setDaemon

设置为前台线程(默认)后台线程

如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止

如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止

t=thread.Thread......

t.setDeamon(Ture or False)

t.start()

join 逐个执行每个线程,执行完毕后继续往下执行...就是先把其它前台线程都执行完了才去执行主线程 t.start()
t.join()
    #执行到这儿后返回执行其它线程,可以有参数,比如加个2,就是等2s,2s上面的线程没执行完,就往下执行主线程了。
run 线程被cpu调度后执行此方法  

线程锁:

如果有一个变量,每个线程获取它后都+=1,那么这时就会出现抢占,而且每个线程拿到的变量数据不一样,有可能是计算之前的,也有可能是计算之后的,最终影响了最后的计算结果。

未加线程锁的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import threading
import time
 
gl_num = 0
 
def show(arg):
    global gl_num   #声明为全局变量
    time.sleep(1)
    gl_num +=1      #1、gl_num=0+1
    print gl_num
 
for i in range(10): #创建10个线程
    t = threading.Thread(target=show, args=(i,))
    t.start()
 
print '主线程已执行完毕。'
运行结果:
1
2
3
4
12
34567
8
99

可以看到,数字都是随机的,并没有得到预想的结果。

加入线程锁:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import threading
import time
 
gl_num = 0
    
lock = threading.RLock()    #RLock是递归,Lock是非递归
 
def Func():
    lock.acquire()  #当某一个线程拿到这个变量就锁上了,别人不能碰
    global gl_num
    gl_num +=1      #第一个线程:gl_num=0+1,第二个:gl_num=1+1,第三个:。。。。。。
    time.sleep(1)
    print gl_num
    lock.release()  #计算完成,释放锁,下一个线程可以去拿了
 
for i in range(10):
    t = threading.Thread(target=Func)
    t.start()
运行结果:
 
1
2
3
4
主线程已执行完毕。
1
2
3

加入了线程锁的程序结果正确了,注意结果的打印顺序,setDaemon是默认的True,所以主线程执行完毕后,等待前台线程也执行完成后,程序停止。

event:

setDaemon的作用主要是主线程是否等待其他线程,而这个event(python线程的事件)就可以控制其它的线程,通过设定标志位控制子线程何时执行,它(通过三个方法:set、wait、clear)能让子线程停下来,也能够让子线程继续。

三个方法,一个字段

三个方法:set、wait、clear

事件处理的机制:

在event内部定义了一个标志位“Flag”,这个“Flag”你看不见,当:

  1. 如果“Flag”值为 False(执行event_obj.clear()设定),那么当程序执行 event.wait( )时就会阻塞;

  2. 如果“Flag”值为True(执行event_obj.set()设定,那么event.wait 方法时便不再阻塞。

代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import threading
  
def do(event):
    print 'start'
    event.wait()
    print 'execute'
  
event_obj = threading.Event()
for i in range(10):
    t = threading.Thread(target=do, args=(event_obj,))
    t.start()
  
event_obj.clear()
inp = raw_input('input:')
if inp == 'true':
    event_obj.set()

使用event,要先实例化,创建event_obj,然后才能执行它里面的三个方法set、wait、clear

clear( ):将“Flag”设置为False

set( ):将“Flag”设置为True

执行结果:
1
2
3
4
5
6
7
8
9
start
start
start
……
 input:true
execute
execute
execute
……

开始,主线程先把标志位设定为False,每个子线程执行了打印“start”任务,然后因为event.wait()就阻塞住了,直到input:true,标志位设定成了True,下面的打印“execute”任务才会被执行。

多进程

1
2
3
4
5
6
7
8
from multiprocessing import Process
 
def foo(i):
    print 'say hi',i
 
for i in range(10):
    p = Process(target=foo,args=(i,))
    p.start()

和创建多线程代码类似,只不过导入的模块不一样而已。

创建和cpu-cores相等的进程,能最大化利用cpu,也是最合理的数量。

多进程因为内存不能共享,所以创建进程需要非常大的系统资源开销。数据怎么才能共享呢?

进程间数据共享:

1、用一个特殊的数据结构,Array数组,声明后,其它进程能使用了。

1
2
3
4
5
6
7
8
9
10
11
12
#方法一,公共数组Array
from multiprocessing import Process,Array
temp = 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()

2、公共数据字典manage.dict()

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#方法二: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()

既然进程之间数据能够共享,那么势必会造成争抢,形成脏数据,so,和进程一样,要加一个锁。

进程锁:

进程锁的使用方法和线程锁一样,只不过调用的方法不同而已。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from multiprocessing import Process, Array, RLock
 
def 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()

进程池:

Pool可以提供指定数量的进程供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执行它。

创建进程池代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from  multiprocessing import Process,Pool
import time
 
def Foo(i):
    time.sleep(2)
    return i+100
 
def Bar(arg):   #arg是Foo函数的返回值
    print arg
 
pool = Pool(5#创建5个池进程
#Pool类怎么来的?from multiprocessing导入的时候,加载了__init__.py中的Pool函数,这个函数又把pool.py中的class Pool加载进内存
 
print pool.apply(Foo,(1,))  #同步模式,一个一个执行
 
print pool.apply_async(func =Foo, args=(1,)).get()  #异步模式,同时执行所有进程
 
for i in range(10): #进程池只有5个进程连接,所以会5个5个执行
    pool.apply_async(func=Foo, args=(i,),callback=Bar)  #执行完func再执行callback
 
print 'end'
pool.close()    #不再接受新的请求
#pool.terminate()    #立即关闭,不管执行完没有,如果写了这句,后面的join就没有意义了。
pool.join()     #进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。
执行结果:

注意:在windows上执行要加入 if __name__=="__main__": ,否则会报错。

        The "freeze_support()" line can be omitted if the program

                    is not going to be frozen to produce a Windows executable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@localhost ]# python process_pool.py
101
101
end
100
101
102
103
104
105
106
107
108
109

 

函数解释:
  • apply_async(func[, args[, kwds[, callback]]]) 它是非阻塞,apply(func[, args[, kwds]])是阻塞的;

  • close()    关闭pool,使其不在接受新的任务。

  • terminate()    结束工作进程,不再处理未完成的任务。

  • join()    主进程阻塞,等待子进程的退出, join方法要在close或terminate之后使用。

部分来源:http://www.cnblogs.com/kaituorensheng/p/4465768.html

协程

相关概念:

定义:

对线程的一个分片

与进程、线程的区别:

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

存在的意义:

对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(上下文切换开销,保存状态,下次继续)。

协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序,效率更高

适用场景:

当程序中存在大量不需要CPU的操作时(IO),适用于协程;如,网络爬虫。

协程代码:greenlet(需要安装,用处不大。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 参考《python安装.egg文件》安装
 
from greenlet import greenlet
 
def test1():
    print 1
    gr2.switch()    #切换到执行test2函数,并记录执行到这里的标记,下次再switch回来的时候,从这里接着执行。
    print 2
    gr2.switch()
 
def test2():
    print 3
    gr1.switch()
    print 4
 
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()        #和yield相似,swithch()的时候先暂停,执行下一个。
执行结果:
 
1
2
3
4
1
3
2
4

greenlet是主动切换,当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。

由于IO操作非常耗时,经常使程序处于等待状态,第三方的gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。

genvent:

genvent是对greenlet的一个封装,同时发多个IO请求,不阻塞。

由于切换是在IO操作时自动完成,所以gevent需要修改Python自带的一些标准库,这一过程在启动时通过monkey patch完成:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from gevent import monkey; monkey.patch_all()    
import gevent
import urllib2
 
def 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
2
3
4
5
6
GET: https://www.python.org/
GET: https://www.yahoo.com/
GET: https://github.com/
45661 bytes received from https://www.python.org/.
14823 bytes received from https://github.com/.
304034 bytes received from https://www.yahoo.com/.

从结果看,3个网络操作是并发执行的,而且结束顺序不同,但只有一个线程。

部分来源:http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001407503089986d175822da68d4d6685fbe849a0e0ca35000

 





原文地址:https://www.cnblogs.com/daliangtou/p/5103442.html