python多线程

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python多线程相关的知识,希望对你有一定的参考价值。

首先,说明一下多线程的应用场景:当python处理多个任务时,这些任务本质是异步的,需要有多个并发事务,各个事务的运行顺序可以是不确定的、随机的、不可预测的。计算密集型的任务可以顺序执行分隔成的多个子任务,也可以用多线程的方式处理。但I/O密集型的任务就不好以单线程方式处理了,如果不用多线程,只能用一个或多个计时器来处理实现。

      下面说一下进程与线程:进程(有时叫重量级进程),是程序的一次执行,正如我们在centos中,ps -aux | grep something 的时候,总有一个他自身产生的进程,也就是这个grep进程,每个进程有自己的地址空间、内存、数据栈、及其他记录其运行轨迹的辅助数据,因此各个进程也不能直接共享信息,只能用进程间通信(IPC)。

      线程(轻量级进程),与进程最大的区别是,所有的线程运行在同一个进程中,共享相同的运行环境,共享同一片数据空间。所以线程之间可以比进程之间更方便的共享数据以及相互通讯,并发执行完成事务。

      为了方便理解记忆进程与线程的关系,我们可以做一个类比:把cpu比作一个搬家公司,而这家搬家公司只有一辆车(进程)来供使用,开始,这家搬家公司很穷,只有一个员工(单线程),那么,这个搬家公司一天,最多只能搬5家,后来,老板赚到钱了,他没买车,而是多雇了n个员工(多线程),这样,每个员工会被安排每次只搬一家,然后就去休息,把车让出来,让其他人搬下一家,这看起来其实并没有提高多少效率,反而增加了成本是吧,这是因为GIL(Global Interpreter Lock) 全局解释器锁,保证了线程安全(保证数据被安全读取),即同时只能有一个线程在CPU上运行,这是python特有的机制,也就是说,即使你的运行环境具有双CPU,python虚拟机也只会使用一个cpu,也就是说GIL 直接导致 CPython 不能利用物理多核的性能加速运算。具体的详细解释(历史遗留问题,硬件发展太快)可以参考这篇博客:

      http://blog.sina.com.cn/s/blog_64ecfc2f0102uzzf.html

      在python核心编程中,作者极力建议我们不要使用thread模块,而是要使用threading模块,原因如下:

   1、当主线程退出时,所有其他线程没有被清除就退出了,thread模块无法保护所有子线程的安全退出。即,thread         模块不支持守护进程。

   2、thread模块的属性有可能会与threading出现冲突。

   3、低级别的thread模块的同步原语很少(实际只有一个,应该是sleep)。

一、thread模块 

 以下是不使用GIL和使用GIL的两个示例代码:

 1.不使用GIL的代码示例:

 1 from time import sleep,ctime
 2 import thread
 3 
 4 def loop0():
 5     print start loop 0 at: ,ctime()
 6     sleep(4)
 7     print loop 0 done at: ,ctime()
 8 def loop1():
 9     print start loop 1 at: ,ctime()
10     sleep(2)
11     print loop 1 done at: ,ctime()
12 def main():
13     print start at: ,ctime()
14     thread.start_new_thread(loop0,())
15     thread.start_new_thread(loop1,())
16     sleep(6)
17     print all loop is done,  ,ctime()
18 
19 if __name__==__main__:
20     main()
21  
22 
23 输出结果:
24 
25 start at:  Thu Jan 28 10:46:27 2016
26 start loop 0 at:   Thu Jan 28 10:46:27 2016
27 
28 start loop 1 at:   Thu Jan 28 10:46:27 2016
29 loop 1 done at:  Thu Jan 28 10:46:29 2016
30 loop 0 done at:  Thu Jan 28 10:46:31 2016
31 all loop is done,  Thu Jan 28 10:46:33 2016

由以上输出可以看出,我们成功开启了两个线程,并且与主线程同步,在第2s时,loop1先完成,第4s时loop0完成,又过了2s,主线程完成结束。整个主线程经过了6s,loop0和loop1同步完成。

 

2、使用GIL的代码示例:

 

 1 import thread
 2 from time import sleep,ctime
 3 loops = [4,2]
 4 def loop(nloop,nsec,lock):
 5     print start loop,nloop,at: ,ctime()
 6     sleep(nsec)
 7     print loop,nloop,done at:,ctime()
 8     lock.release()
 9 def main():
10     print starting at:,ctime()
11     locks = []
12     nloops = range(len(loops))
13 
14     for i in nloops:
15         lock = thread.allocate_lock()                          #创建锁的列表,存在locks中
16         lock.acquire()                         
17         locks.append(lock)                                      
18     for i in nloops:
19         thread.start_new_thread(loop,(i,loops[i],locks[i]))    #创建线程,参数为循环号,睡眠时间,锁
20     for i in nloops:
21         while locks[i].locked():                              #等待循环完成,解锁
22             pass
23     print all DONE at:,ctime()
24 if __name__ == __main__:
25     main()
26  
27 
28 以上输出如下:
29 
30 starting at: Thu Jan 28 14:59:22 2016
31 start loop  0  at:   Thu Jan 28 14:59:22 2016
32 
33 start loop  1  at:   Thu Jan 28 14:59:22 2016
34 loop 1 done at: Thu Jan 28 14:59:24 2016
35 loop 0 done at: Thu Jan 28 14:59:26 2016
36 all DONE at: Thu Jan 28 14:59:26 2016

 

历时4秒,这样效率得到提高,也比在主线程中用一个sleep()函数来计时更为合理。

 

二、threading模块

1、Thread类

在thread类中,可以用以下三种方法来创建线程:

(1)创建一个thread实例,传给它一个函数

(2)创建一个thread实例,传给它一个可调用的类对象

(3)从thread派生出一个子类,创建这个子类的对象

方法(1)

 1 __author__ = dell
 2 import threading
 3 from time import sleep,ctime
 4 def loop0():
 5     print start loop 0 at:,ctime()
 6     sleep(4)
 7     print loop 0 done at:,ctime()
 8 def loop1():
 9     print start loop 1 at:,ctime()
10     sleep(2)
11     print loop 1 done at:,ctime()
12 def main():
13     print starting at:,ctime()
14     threads = []
15     t1 = threading.Thread(target=loop0,args=())          #创建线程
16     threads.append(t1)
17     t2 = threading.Thread(target=loop1,args=())
18     threads.append(t2)
19     for t in threads:
20         t.setDaemon(True)<span style="white-space:pre">    </span>      #开启守护线程(一定要在start()前调用)
21         t.start()<span style="white-space:pre">        </span>      #开始线程执行
22     for t in threads:<span style="white-space:pre">                    </span>
23         t.join()<span style="white-space:pre">        </span>      #将程序挂起阻塞,直到线程结束,如果给出数值,则最多阻塞timeout秒
24 
25 if __name__ == __main__:
26     main()
27     print All DONE at:,ctime()
28 
29 在这里,就不用像thread模块那样要管理那么多锁(分配、获取、释放、检查等)了,同时我也减少了循环的代码,直接自己编号循环了,得到输出如下:
30  
31 
32 starting at: Thu Jan 28 16:38:14 2016
33 start loop 0 at: Thu Jan 28 16:38:14 2016
34 start loop 1 at: Thu Jan 28 16:38:14 2016
35 loop 1 done at: Thu Jan 28 16:38:16 2016
36 loop 0 done at: Thu Jan 28 16:38:18 2016
37 All DONE at: Thu Jan 28 16:38:18 2016

结果相同,但是从代码的逻辑来看,要清晰的多了。其他两种在此就不贴出代码了。实例化一个Thread与调用thread.start_new_thread直接最大的区别就是新的线程不会立即开始执行,也就是说,在threading模块的Thread类中当我们实例化之后,再调用.start()函数后被统一执行,这使得我们的程序具有很好的同步特性。

下面是单线程与多线程的一个对比示例,分别以乘除完成两组运算,从而看出多线程对效率的提高

 1 from time import ctime,sleep
 2 import threading
 3 
 4 def multi():
 5     num1 = 1
 6     print start mutiple at:,ctime()
 7     for i in range(1,10):
 8        num1 = i*num1
 9        sleep(0.2)
10     print mutiple finished at:,ctime()
11     return num1
12 def divide():
13     num2 = 100
14     print start division at:,ctime()
15     for i in range(1,10):
16         num2 = num2/i
17         sleep(0.4)
18     print division finished at:,ctime()
19     return num2
20 def main():
21     print ---->single Thread
22     x1 = multi()
23     x2 = divide()
24     print The sum is ,sum([x1,x2]),\nfinished singe thread,ctime()
25 
26     print ----->Multi Thread
27     threads = []
28     t1 = threading.Thread(target=multi,args=())
29     threads.append(t1)
30     t2 = threading.Thread(target=divide,args=())
31     threads.append(t2)
32     for t in threads:
33         t.setDaemon(True)
34         t.start()
35     for t in threads:
36         t.join()
37 
38 if __name__ == __main__:
39     main()
40 
41 结果如下:
42 
43  
44 
45 ---->single Thread
46 
47 start mutiple at: Thu Jan 28 21:41:18 2016
48 
49 mutiple finished at: Thu Jan 28 21:41:20 2016
50 
51 start division at: Thu Jan 28 21:41:20 2016
52 
53 division finished at: Thu Jan 28 21:41:24 2016
54 
55 The sum is  362880 
56 
57 finished singe thread Thu Jan 28 21:41:24 2016
58 
59 ----->Multi Thread
60 
61 start mutiple at: Thu Jan 28 21:41:24 2016
62 
63 start division at: Thu Jan 28 21:41:24 2016
64 
65 mutiple finished at: Thu Jan 28 21:41:26 2016
66 
67 division finished at: Thu Jan 28 21:41:27 2016
68 
69 The sum is : 362880

 

以上是关于python多线程的主要内容,如果未能解决你的问题,请参考以下文章

[Python3] 043 多线程 简介

python中的多线程和多进程编程

多线程 Thread 线程同步 synchronized

多个用户访问同一段代码

在 Python 多处理进程中运行较慢的 OpenCV 代码片段

线程学习知识点总结