您的位置:首页 > 编程语言 > Python开发

Python求索之路7——线程&进程

2016-03-14 17:43 363 查看
线程

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

threading模块:

threading模块主要用于线程相关的操作。

线程方法:

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

setName 为线程设置名称

getName 获取线程名称

setDaemon 设置为后台线程或前台线程(默认), 如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止。如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止。

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

run 线程被cpu调度后自动执行线程对象的run方法

调用线程的两种方式:

1、直接调用

import threading
from time import sleep
def mythread(num):#定义每个线程要执行的操作(线程要做的事情)
print 'the thread num is %s' % num
sleep(3)

if __name__ == '__main__':
t1 = threading.Thread(target=mythread,args=(1,))#实例化一个线程,执行mythread函数,传递参数1
t2 = threading.Thread(target=mythread,args=(2,))
t1.start()#启动线程1
t2.start()
print t1.getName()#获取线程1的名字
print t2.getName()

执行结果:
the thread num is 1
the thread num is 2
Thread-1
Thread-2


线程1和线程2同时执行完毕,然后sleep(3)。并不是执行完线程1后sleep(3),然后再执行线程2。因此两个线程是并行执行。

2、继承调用

import threading
from time import sleep

class mythread(threading.Thread):
def __init__(self,num):
threading.Thread.__init__(self)#先继承父类中的构造方法(__init__)
self.num = num

def run(self):#必须有一个run方法
print 'the thread num is %s ' % self.num
sleep(3)

if __name__ == '__main__':
t1 = mythread(1)
t2 = mythread(2)

t1.start()
t2.start()
print t1.getName()
print t2.getName()


执行结果同上。

主线程等待子线程:

以上两个例子中,主线程启动了两个子线程,并和两个子线程同时执行。如果让主线程等待子线程执行完毕后再执行,可以如下操作:

import threading
from time import sleep
def mythread(num):
print 'the thread num is %s' % num
sleep(3)

if __name__ == '__main__':
t1 = threading.Thread(target=mythread,args=(1,))
t2 = threading.Thread(target=mythread,args=(2,))
t1.start()
t2.start()
print t1.getName()
print t2.getName()

t1.join()#主线程等待t1执行完毕
t2.join()#主线程等待t2执行完毕

print '---main---'#两个子线程执行完,等待3秒,主线程执行。


同时启动10个线程,主线程与子线程同时执行:

import threading
from time import sleep
def mythread(num):
print 'the thread num is %s' % num
sleep(3)

if __name__ == '__main__':
for i in range(10):
t = threading.Thread(target=mythread,args=(i,))
t.start()
print '---main---'


同时启动10个线程,主线程等待子线程执行完后再执行:

import threading
from time import sleep
def mythread(num):
print 'the thread num is %s' % num
sleep(3)

t_list = []

if __name__ == '__main__':
for i in range(10):
t = threading.Thread(target=mythread,args=(i,))
t.start()
t_list.append(t)#将子线程加到列表中
for i in t_list:
t.join()#等待列表中的全部子线程执行完毕,主线程再执行
print '---main---'


守护进程:

setDaemon实例1:

import time
import threading

def run(n):
print '%s running,,,' % n
time.sleep(2)
print 'done...'

def main():
for i in range(5):
t = threading.Thread(target=run,args=(i,))
t.start()
t.join(1)
print 'start %s' % t.getName()

m = threading.Thread(target=main,args=())
m.setDaemon(True)#将主线程设置为Daemon(守护)线程,它退出时,其它子线程会同时退出,不管是否执行完任务
m.start()
m.join(2)
print 'main thread done...'

#执行结果:
0 running,,,
start Thread-2
1 running,,,
main thread done...


setDaemon实例2:

import time
import threading

def run(n):
print '%s running,,,' % n
time.sleep(2)
print 'done...'

def main():
for i in range(5):
t = threading.Thread(target=run,args=(i,))
t.start()
t.join(1)
print 'start %s' % t.getName()

m = threading.Thread(target=main,args=())
m.setDaemon(True)
m.start()
#m.join(2)
print 'main thread done...'

#执行结果:
main thread done...


GIL(Glogbal Interpreter Lock):

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)


在 CPython中,某一时刻只有一个线程在运行。

首先需要明确的一点是
GIL
并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把
GIL
归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL。

线程锁/互斥锁:

一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据。此时,如果多个线程同时要修改同一份数据,那么将会导致线程运算结果不准确。

eg:

import time
import threading

def add():
global num
time.sleep(1)#打乱每个线程的顺序
num -=1
print num

num = 10
for i in range(10):
t = threading.Thread(target=add,args=())
t.start()

#执行结果:
9
8
6
7
5
4
3
2
1
0


由结果可以看出,多个线程同时操作一份共享数据,导致将结果不可预期,也称“线程不安全”。

要使结果准确,需要确保某一时刻只有一个线程可以对数据进行操作———线程锁。

eg:

import time
import threading

def add():
global num
time.sleep(1)#打乱每个线程的顺序
lock.acquire()#添加一个进程锁
num -=1
print num
lock.release()#解除进程锁

lock = threading.Lock()#实例化一个lock
num = 10
for i in range(10):
t = threading.Thread(target=add,args=())
t.start()

#运行结果:
9
8
7
6
5
4
3
2
1
0


递归锁:

在大锁中还要包含小锁:

def run1():
print("grab the first part data")
lock.acquire()
global num
num +=1
lock.release()
return num
def run2():
print("grab the second part data")
lock.acquire()
global  num2
num2+=1
lock.release()
return num2
def run3():
lock.acquire()#在运行run1和run2之前加锁,确保执行完run1后接着执行run2,并且在这过程中没有其他的线程来执行run1和run2
res = run1()
print('--------between run1 and run2-----')
res2 = run2()
lock.release()
print(res,res2)

if __name__ == '__main__':

num,num2 = 0,0
lock = threading.RLock()
for i in range(10):
t = threading.Thread(target=run3)
t.start()

while threading.active_count() != 1:
print(threading.active_count())
else:
print('----all threads done---')
print(num,num2)


GIL(全局锁)与线程锁:

既然有GIL了,为什么还要线程锁呢???

GIL是防止底层多个C原生线程(CPython)同一时间修改同一数据,就是说python解释器不能并行的执行代码。线程锁是为了防止多个线程同时修改同一份数据。

多进程:

p2.start()
print '===main===

#执行结果:
===main===
hello ahaii
hello tom


主进程等待子进程执行完毕,再执行:

import multiprocessing
from time import sleep

def say(name):
print 'hello %s' % name
sleep(2)
if __name__ == '__main__':
p1 = multiprocessing.Process(target=say,args=('ahaii',))
p2 = multiprocessing.Process(target=say,args=('tom',))
p1.start()
p2.start()
p1.join()
p2.join()
print '===main==='

#执行结果:
hello ahaii
hello tom#执行完后,等待2s,执行主进程。
===main===


进程之间通信

线程之间可以相互访问,而不同进程之间内存不是共享的,不能相互访问。因此,要实现通信,需要通过第三方方法(管道)。

Queue队列

python中,队列是进程之间进行数据交换的主要形式。Queue模块提供了队列的操作。无论多少个线程向队列中放数据或者取数据,Queue同一时刻只允许一个线程在操作,即它自带锁。

Queue.put():向队列中放数据

Queue.get():从队列中取数据

Queue.qsize():查看队列中剩余数据个数

常用方法:

import Queue

#q = Queue.Queue(maxsize=3)#最大长度
#print q.get()
#print q.get(timeout=2)
q = Queue.PriorityQueue(maxsize=3)#优先级队列

q.put((3,[1,2,3]))#第一组数据为优先级,第二组为数据
q.put((1,22))
q.put((2,33))
#q.put(5)#队列设置长度为3,第四次put时,由于队列已满,会发生阻塞。可以设置超时时间避免阻塞,如:q.put(5,timeout=2)
# print q.qsize()
# print q.full()#判断队列是否已满,False or True
# print q.empty()#判断队列是否已空,False or True
print q.get()
print q.get()
print q.get()

#执行结果:
(1, 22)
(2, 33)
(3, [1, 2, 3])


默认Queue队列遵循先进先出的原则,如有特殊要求,可以使用优先级队列。在优先级队列中,优先级最小,权重越大,越现被取到。

eg:

from multiprocessing import Process,Queue

def func(q):
q.put([123,'hello'])

if __name__ == '__main__':
q = Queue()
p1 = Process(target=func,args=(q,))#子进程执行func函数,向队列中put数据
p1.start()
print q.get()#父进程向队列中get数据
p1.join()

#执行结果:
[123, 'hello']


Queue队列在取数据时,时按照数据存放的先后顺序,每次只能存、取一个数据,顺序遵循“先进先出”的原则。

多次存、取数据:

eg:

from multiprocessing import Process,Queue

def func(q):
q.put([123,'hello'])

if __name__ == '__main__':
q = Queue()
p1 = Process(target=func,args=(q,))#子进程执行func函数,向队列中put数据
p2 = Process(target=func,args=(q,))#子进程执行func函数,向队列中put数据
p1.start()
p2.start()
print q.get()#父进程向队列中get数据
print q.get()
p1.join()
p2.join()

#执行结果:
[123, 'hello']
[123, 'hello']


Managers:

from multiprocessing import Manager,Process

def f(d,l):#传入一个字典和一个列表
d[1] = '1'#修改字典中的值
d['2'] = 2
d[0.25] = None
l.append(1)#列表追加1
print l

if __name__ == '__main__':
with Manager() as manager:#这种写法好处是,manager执行完后自动销毁。
#manager = Manager()
d = manager.dict()#生成一个字典
l = manager.list(range(5))#生成一个列表
p_list = []
for i in range(10):#生成10个进程
p = Process(target=f,args=(d,l))
p.start()
p_list.append(p)
for res in p_list:
res.join()#等待10个线程执行完毕
print d#主进程执行
print l

#执行结果:
[0, 1, 2, 3, 4, 1]
[0, 1, 2, 3, 4, 1, 1]
[0, 1, 2, 3, 4, 1, 1, 1]
[0, 1, 2, 3, 4, 1, 1, 1, 1]
[0, 1, 2, 3, 4, 1, 1, 1, 1, 1]
[0, 1, 2, 3, 4, 1, 1, 1, 1, 1, 1]
[0, 1, 2, 3, 4, 1, 1, 1, 1, 1, 1, 1]
[0, 1, 2, 3, 4, 1, 1, 1, 1, 1, 1, 1, 1]
[0, 1, 2, 3, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[0, 1, 2, 3, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
{0.25: None, 1: '1', '2': 2}
[0, 1, 2, 3, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]


由结果可以看出,每个子进程都向同一个列表中追加了1。

进程池:

由于进程比较消耗资源,因此当系统中运行多个进程时,需要设定一个限制。进程池就是设定系统中同一时刻,最多有多少个进程执行。

eg:

from  multiprocessing import Process,Pool
import time

def Foo(i):
time.sleep(2)
#    print 'hello'
return i+100

def Bar(arg):
print('-->exec done:',arg)

pool = Pool(3)#限制最多执行5个进程

for i in range(10):
pool.apply_async(func=Foo, args=(i,),callback=Bar)#异步,执行完Foo后,接着执行Bar(回调),
#    pool.apply(func=Foo, args=(i,))#同步,每个进程串行

print('end')
pool.close()
pool.join()#进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: