多线程编程
什么是多线程,线程是操作系统能够进行运算调度的最小单位。他包含在进程之中,是进程中的实际运作单位。线程是进程中一个单顺序的空值六,一个进程可以并发多个线程,每个线程可以并行处理不同的任务。
threading模块
python的标准库提供了两个模块用于多线程处理,_thread和threading,_thread是低级模块,threading是高级模块,是对_thread进行了封装。
启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行:
线程有两种调用方式:直接调用和继承式调用
直接调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
#!/usr/bin/env python # coding:utf-8 ''' Created on: 2016年5月16日 @author: 张晓宇 Email: 61411916@qq.com Version: 1.0 Description: 多线程演示程序,直接启动方式 Help: ''' def sayhi(num): # 定义每隔线程都要运行的函数 print ( '%s is say hi' % num) import time time.sleep( 3 ) import threading if __name__ = = '__main__' : t1 = threading.Thread(target = sayhi, args = [ 1 , ]) # 调用Thread方法生成一个线程实例,第一个参数tartget表示进程要执行的函数,args表示要传递给进程函数的参数 t2 = threading.Thread(target = sayhi, args = [ 2 , ]) t1.start() # 启动进程 t2.start() t1.join() # 等待子进程完毕,这句话的意思就等待一个进程执行完在执行这句话后面的逻辑,join方法还可以接收一个超时时间参数,表示最多等待多长时间,超过这个时间就不等了,继续执行下面的语句,注意,是不等待,不是中断进程的执行 t2.join() print (t1.getName()) # getName()表示获取进程的名称,默认Thread-1、Thread-2...这种命名方式 print (t2.getName()) |
继承式调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
#!/usr/bin/env python # coding:utf-8 ''' Created on: @author: 张晓宇 Email: 61411916@qq.com Version: 1.0 Description: 多线程演示程序,继承式调用 Help: ''' import threading class Mythreading(threading.Thread): ''' 定义一个类,继承自threading.Thread ''' def __init__( self , num): ''' 初始化方法 :param num: :return: ''' threading.Thread.__init__( self ) self .num = num def run( self ): ''' 重写run方法,也就是每个线程要执行的函数 :return: ''' print ( '%s is say hi' % self .num) import time time.sleep( 5 ) if __name__ = = '__main__' : t1 = Mythreading( 1 ) # 用刚才定义的类创建进程对象 t2 = Mythreading( 2 ) t1.start() t2.start() t1.join() t2.join() print (t1.getName()) print (t2.getName()) |
守护线程
默认情况下,线程执行完毕如果该线程下还有子线程没有执行完毕才会结束,我们可以通过守护线程的方式,是的主线程执行完毕强制结束下面的子线程的运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
#!/usr/bin/env python3 # coding:utf-8 ''' Created on: @author: 张晓宇 Email: 61411916@qq.com Version: 1.0 Description: 守护线程演示程序 Help: ''' import time import threading def child(n): ''' 子线程执行的函数 :param n: :return: ''' print ( '[%s]------running----
' % n) time.sleep( 2 ) print ( '--done--' ) def main(): ''' 主线程要执行的函数 :return: ''' for i in range ( 2 ): # 循环生成5个子线程 t = threading.Thread(target = child,args = [i,]) t.start() print ( 'starting thread' , t.getName()) m = threading.Thread(target = main,args = []) # 创建主线程对象 m.setDaemon( True ) #将主线程设置为Daemon线程,它退出时,其它子线程会同时退出,不管是否执行完任务 m.start() # 启动主线程 m.join() # 等待主线程执行完毕 print ( "---main thread done----" ) |
输出结果
1
2
3
4
5
6
7
|
[ 0 ] - - - - - - running - - - - starting thread Thread - 2 [ 1 ] - - - - - - running - - - - starting thread Thread - 3 - - - main thread done - - - - |
可以看出,main线程下的子线程执行的时间要比main线程时间长,当main线程执行完的时候,子线程的print
(
'--done--'
)还没有执行就被强制结束了
锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
#!/usr/bin/env python # coding:utf-8 ''' Created on: @author: 张晓宇 Email: 61411916@qq.com Version: 1.0 Description: 线程锁演示程序 Help: ''' import threading import time def addNum(): global num # 调用全局变量num print ( '--get num:' , num) time.sleep( 1 ) #lock.acquire() num + = 1 # 每个线程都对num进行加1操作 #lock.release() print (num) if __name__ = = '__main__' : lock = threading.Lock() num = 0 thread_list = [] # 初始化一个线程列表 for i in range ( 10000 ): # 循环启动10000个进程 t = threading.Thread(target = addNum) t.start() thread_list.append(t) # 加入到线程列表中 for t in thread_list: # 循环等待线程列表里的所有线程结束 t.join() print (num) # 打印num的最终值 |
正常情况,启动10000个线程,每个线程对num做加1操作,最终的结果将是10000,但是如果你用python2.X版本的解释器执行上面的代码会发现,最结果不总是10000,而且随着线程越多,这个最终结果的值稳定性越差,这就需要在对需要修改线程间共享的变量的时候加一把锁,当一个线程获得锁并操作一个共享变量的时候,其他线程只能等待锁释掉才可以,等待的过程是阻塞的,只有锁被释放掉,其他线程中“抢”到锁的进程才可以继续进行
申请锁
1
|
lock.acquire() |
释放锁
1
|
lock.release() |
注意,Python3.X已经修复了这个问题,不用加锁结果也是正确的
递归锁
递归锁说白了就是一个大锁里面套着小锁,也就是子锁,用到的地方不多,就不说了
信号量(Semaphore)
刚才说的锁也是互斥锁,同事只能有一个线程操作,而Semaphore可以同事允许一定数量的线程更改数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import threading,time def run(n): semaphore.acquire() time.sleep( 1 ) print ( "run the thread: %s
" % n) semaphore.release() if __name__ = = '__main__' : num = 0 semaphore = threading.BoundedSemaphore( 5 ) #最多允许5个线程同时运行 for i in range ( 20 ): t = threading.Thread(target = run,args = (i,)) t.start() while threading.active_count() ! = 1 : pass #print threading.active_count() else : print ( '----all threads done---' ) print (num) |
事件Event
事件可以裂解为就是一个信号,他只有两个状态可以理解为真和假,常用方法如下
-
set():相当于设置为真
-
clear():相当于设置为假
-
isSet():判断是否为真
-
wait():等待事件置为真(阻塞)
事件做常用的一个地方就是红绿灯模型,下面代码这就是演示红绿灯模型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
#!/usr/bin/env python # coding:utf-8 ''' Created on: 2016年3月5日 @author: 张晓宇 Email: 61411916@qq.com Version: 1.0 Description: 红绿灯模型演示程序 Help: ''' import threading def light(): ''' 信号灯进程的线程执行的函数 :return: ''' import time if not event.isSet(): # 判断是否为真,如果不为真就设置为真,也就是一上来是绿灯 event. set () count = 0 # 初始化计数器,可以理解为红绿灯之间的等待时间 while True : # 无限制循环下去 if count < 10 : # 0-10秒是绿灯 print ( ' 33[42;1m--green light on-- 33[0m' ) elif count < 13 : # 10-12是黄灯 print ( ' 33[43;1m--yellow light on-- 33[0m' ) elif count < 20 : # 14-19秒是红灯 if event.isSet(): # 判断如果为真,就设置成假的 event.clear() print ( ' 33[41;1m--red light on-- 33[0m' ) else : # 第20秒的时候计数器归零,并从新设置为真 count = 0 if not event.isSet(): event. set () time.sleep( 1 ) count + = 1 # 计数器加1 def car(n): ''' 汽车进程要执行的函数 :param n: :return: ''' import time while True : time.sleep( 1 ) # 每隔1秒检查一下红绿灯状态 if event.isSet(): # 如果为真就runing print ( 'car [%s] is running...' % n) else : # 否则就waiting print ( 'car [%s] is waiting for the red light...' % n) event.wait() # 等待事件变为真 if __name__ = = '__main__' : event = threading.Event() # 创建Event对象 Light = threading.Thread(target = light) # 创建信号灯线程 Light.start() # 启动线程 for i in range ( 3 ): # 循环创建3个汽车线程对象,并启动 t = threading.Thread(target = car, args = [i,]) t.start() |