目录
一、概念
1、进程
2、线程
3、协程
二、区别
1、进程与线程比较
2、协程与线程比较
三、线程
1、基本使用
2、更多方法:
3、线程锁
4、event
5、小结
四、进程
1、基本使用
2、进程数据共享
3、使用特殊的数据类型,来进行穿墙
4、进程锁
5、进程池
6、小结
五、协程
1、greenlet
2、gevent
3、遇到IO操作自动切换
4、小结
想法产生看法,看法产生做法。
概念:
进程是一个实体。
每个进程都有自己的地址空间(CPU分配)。实体空间包括三部分:
文本区域:存储处理器执行的代码
数据区域:存储变量或进程执行期间使用的动态分配的内存。
堆栈:进程执行时调用的指令和本地变量。
进程是一个“执行中的程序”。
程序是指令与数据的有序集合,程序本身是没有生命的,只有CPU赋予程序生命时(CPU执行程序),它才能成为一个活动的实体,称为“进程”。
概括来说,进程就是一个具有独立功能的程序在某个数据集上的一次运行活动
特点:
动态性:进程是程序的一次执行过程,动态产生,动态消亡。
独立性:进程是一个能独立运行的基本单元。是系统分配资源与调度的基本单元。
并发性:任何进程都可以与其他进程并发执行。
概念
线程的实体包括程序,数据,TCB。TCB包括:
线程是指进程内的一个执行单元,也是进程内的可调度实体。线程与进程的区别:
python提供了两个模块来实现多线程thread 和threading ,thread 有一些缺点,在threading 得到了弥补,为了不浪费时间,所以我们直接学习threading 就可以了。
Threading用于提供线程相关的操作,线程是应用程序中工作的最小单元。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import threading
import time
def show(arg):
time.sleep(1)
print 'thread'+str(arg)
for i in range(10): #开启10个线程
t = threading.Thread(target=show, args=(i,)) #创建线程t,使用threading.Thread()方法,在这个方法中调用show方法target=show,args方法对show进行传参。
t.start()
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()
设置线程锁:
#!/usr/bin/env python
#coding:utf-8
import threading
import time
gl_num = 0
lock = 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()
他的作用就是:用主线程控制子线程合适执行,他可以让子线程停下来,也可以让线程继续! 他实现的机制就是:标志位“Flag”
事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。
clear:将“Flag”设置为False
set: 将“Flag”设置为True
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import threading
def do(event):
print 'start'
event.wait() #执行对象wait方法,然后他们停下来,等待“Flag”为True
print 'execute'
event_obj = threading.Event() #创建事件的对象
for i in range(10):
t = threading.Thread(target=do, args=(event_obj,)) #把方法do传到每个线程里面,
t.start()
event_obj.clear() #设置"Flag"为Flase
inp = raw_input('input:')
if inp == 'true':
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()
类型对应表
'c': ctypes.c_char, 'u': ctypes.c_wchar,
'b': ctypes.c_byte, 'B': ctypes.c_ubyte,
'h': ctypes.c_short, 'H': ctypes.c_ushort,
'i': ctypes.c_int, 'I': ctypes.c_uint,
'l': ctypes.c_long, 'L': ctypes.c_ulong,
'f': ctypes.c_float, 'd': ctypes.c_double
既然进程之间可以进行共享数据,如果多个进程同时修改这个数据是不是就会造成脏数据?是不是就得需要锁!
#!/usr/bin/env python
# -*- coding:utf-8 -*-
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()
进程锁
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。
进程池中有两个方法:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from multiprocessing import Process,Pool
import time
def Foo(i):
time.sleep(2)
return i+100
def Bar(arg):
print arg
pool = Pool(5) #创建一个进程池
#print pool.apply(Foo,(1,))#去进程池里去申请一个进程去执行Foo方法
#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()#进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。
'''
apply 主动的去执行
pool.apply_async(func=Foo, args=(i,),callback=Bar) 相当于异步,当申请一个线程之后,执行FOO方法就不管了,执行完之后就在执行callback ,当你执行完之后,在执行一个方法告诉我执行完了
callback 有个函数,这个函数就是操作的Foo函数的返回值!
'''
线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作则是程序员。
协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。
协程的适用场景:当程序中存在大量不需要CPU的操作时(IO),适用于协程;
#!/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),
])
from gevent import monkey; monkey.patch_all()
import gevent
import urllib2
def f(url):
print('GET: %s' % url)
resp = urllib2.urlopen(url) #当遇到I/O操作的时候就会调用协程操作,然后继续往下走,然后这个协程就卡在这里等待数据的返回
data = resp.read()
print('%d bytes received from %s.' % (len(data), url))
gevent.joinall([
gevent.spawn(f, 'https://www.python.org/'), #这里的f是调用这个方法,第二个是调用方的参数
gevent.spawn(f, 'https://www.yahoo.com/'),
gevent.spawn(f, 'https://github.com/'),
])
'''
gevent.spawn(f, 'https://www.python.org/'), #这里的f是调用这个方法,第二个是调用方的参数
当函数f里的代码遇到I/O操作的时候,函数就卡在哪里等待数据的返回,但是协程不会等待而是继续操作!
'''