thread模块提供了基本的线程和锁定支持;而threading模块提供了更高级别、功能更全面的线程管理。
应该避免使用thread模块,尽量使用threading模块,原因如下:
(1)threading模块更加先进,有更好的线程支持,并且thread模块中的一些属性会和threading模块有冲突.
(2)thread模块拥有的同步原语很少,而threading模块有很多。
(3)thread模块对于进程何时退出没有控制。当主线程结束时,所有其他线程也都强制结束,不会发出警告或者进行适当的清理。
python多线程有个全局解释器锁(global interpreter lock),这个锁的意思是任一时间只能有一个线程使用解释器,跟单cpu跑多个程序一个意思,大家都是轮着用的,这叫“并发”,不是“并行”。
多进程间共享数据,可以使用 multiprocessing.Value 和 multiprocessing.Array
在UNIX平台上,当某个进程终结之后,该进程需要被其父进程调用wait,否则进程成为僵尸进程(Zombie)。所以,有必要对每个Process对象调用join()方法 (实际上等同于wait)。对于多线程来说,由于只有一个进程,所以不存在此必要性。
多进程应该避免共享资源。在多线程中,我们可以比较容易地共享资源,比如使用全局变量或者传递参数。在多进程情况下,由于每个进程有自己独立的内存空间,以上方法并不合适。此时我们可以通过共享内存和Manager的方法来共享资源。但这样做提高了程序的复杂度,并因为同步的需要而降低了程序的效率。
为了更好的了解多线程,首先来看个 串行执行的例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| import time
def loop1():
print 'start loop1 at ', time.ctime()
time.sleep(4)
print 'end loop1 at ', time.ctime()
def loop2():
print 'start loop2 at ', time.ctime()
time.sleep(2)
print 'end loop2 at ', time.ctime()
def main():
print 'start main at ', time.ctime()
loop1()
loop2()
print 'end main at ', time.ctime()
if __name__ == '__main__':
main()
|
这个串行执行的例子,先执行loop1 ,再执行loop2 。
由于使用thread模块时,主线程结束时,所有其他线程也都强制结束。下面例子采取让主线程sleep的方式来保证子线程先完成。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| import thread
import time
def loop1():
print 'start loop1 at ', time.ctime()
time.sleep(4)
print 'end loop1 at ', time.ctime()
def loop2():
print 'start loop2 at ', time.ctime()
time.sleep(2)
print 'end loop2 at ', time.ctime()
def main_thread():
print 'start main at ', time.ctime()
thread.start_new_thread(loop1, ())
thread.start_new_thread(loop2, ())
time.sleep(6)
print 'All done at ', time.ctime()
if __name__ == '__main__':
main_thread()
|
由于loop1和loop2函数过于简单,我们可以预估loop1和loop2的完成时间,然后在main_thread函数直接相加两个时间,同时,在4s时,loop1和loop2都已经执行完了,没有实现多线程的优势。在实际情况中,这种方法是十分不可取的。
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
| import time
import thread
loops = [44, 42]
def loop(nsecs, loopname, lock):
print 'start ', loopname, ' at: ', time.ctime()
time.sleep(nsecs)
print 'end ', loopname, ' at: ', time.ctime()
lock.release()
def main():
print 'start main at ', time.ctime()
# locks保存锁对象的列表
locks = []
nloops = range(len(loops))
for i in nloops:
lock = thread.allocate()
lock.acquire()
locks.append(lock)
for i in nloops:
thread.start_new_thread(loop, (loops[i], ('loop' + str(i)), locks[i]))
for i in nloops:
while locks[i].locked():
pass
print 'All done at: ', time.ctime()
if __name__ == '__main__':
main()
|
每个线程将被分配一个已获得的锁。当sleep()的时间到了的时候,释放对应的锁,向主线程表明该线程已完成。
通过使用thread.allocate_lock()函数得到锁对象,然后通过acquire()方法取得(每个锁)。取得锁效果相当于“把锁锁上”。
最后一个循环只是坐在那里等待(暂停主线程),直到所有锁都被释放之后才会继续执行。
上边提到,应该使用更高级的threading模块实现多线程,这里研究使用Thread类来实现多线程,threading模块支持守护线程。使用Thread类,有多种创建线程的方法:
(1)创建Thread 的实例,传给它一个函数。
(2)创建Thread 的实例,传给它一个可调用的类实例。
(3)派生Thread 的子类,并创建子类的实例。
Thread类的常用方法:
方法 | 说明 |
---|
init(group=None, tatget=None, name=None, args=(),kwargs ={}, verbose=None, daemon=None) | 实例化一个线程对象,需要有一个可调用的target,以及其参数args或kwargs。还可以传递name 或group 参数,不过后者还未实现。此外, verbose 标志也是可接受的。而daemon 的值将会设定thread.daemon 属性/标志 |
start() | 开始执行该线程 |
run() | 定义线程功能的方法(通常在子类中被应用开发者重写) |
join(timeout=None) | 直至启动的线程终止之前一直挂起;除非给出了timeout(秒),否则会一直阻塞 |
在第3节的多线程例子中,需要自己管理锁来实现多线程,这样其实比较麻烦。使用threading.Thread类时,就省去了锁的管理,实例化Thread(调用Thread())和调用thread.start_new_thread()的最大区别是新线程不会立即开始执行。
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
| from time import ctime
from time import sleep
import threading
sleepsecs = [4, 8]
def loop(loopname, secs):
print 'start loop',loopname, ' at: ', ctime()
sleep(secs)
print 'end loop',loopname, ' at: ', ctime()
if __name__ == '__main__':
# Thread对象列表
threads = []
for i in range(len(sleepsecs)):
thread = threading.Thread(target=loop, args=(i, sleepsecs[i]))
threads.append(thread)
print 'start main at :', ctime()
# 逐个启动线程
for i in range(len(sleepsecs)):
threads[i].start()
sleep(0.001)
for i in range(len(sleepsecs)):
threads[i].join()
print 'end main at ', ctime()
|
下面添加一个ThreadFunc类,在实例化Thread类时,实际做了2次实例化,包括Thread和ThreadFunc。
当创建新线程时,Thread 类的代码将调用ThreadFunc 对象,此时会调用call()这个特殊方法。
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
| from time import sleep
from time import ctime
from threading import Thread
class ThreadFunc(object):
# func: 多线程要执行的函数 该参数就是一个函数对象
# loopname: 执行函数名称
# secs: sleep等待时间
def __init__(self, func, loopname, secs):
self.func = func
self.loopname = loopname
self.secs = secs
def __call__(self):
self.func(self.loopname, self.secs)
def loop(loopname, secs):
print 'start loop ', loopname, ' at: ', ctime()
sleep(secs)
print 'end loop ', loopname, ' at: ', ctime()
if __name__ == '__main__':
threads = []
secs = [4, 8]
print 'start main at: ', ctime()
for i in range(len(secs)):
thread = Thread(target=ThreadFunc(loop, i, secs[i]))
threads.append(thread)
for i in range(len(secs)):
threads[i].start()
sleep(0.001)
for i in range(len(secs)):
threads[i].join()
print 'end main at:', ctime()
|
派生子类时,只需要创建子类的实例即可,不用再创建Thread类实例。需要注意下边2点:
1.MyThread子类的构造函数必须先调用其基类的构造函数。
2.必须在子类中定义run()方法。
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
| from threading import Thread
from time import ctime
from time import sleep
class MyThread(Thread):
# func:该参数是多线程执行的函数
# args:func函数的参数列表,多个参数的列表
# name:名称
def __init__(self, func, args, name):
# MyThread子类的构造函数必须先调用其基类的构造函数
Thread.__init__(self)
self.func = func
self.args = args
self.name = name
# 必须在子类中定义run()方法
def run(self):
self.func(*self.args)
def loop(nloop, secs):
print 'start loop ', nloop, ' at: ', ctime()
sleep(secs)
print 'end loop ', nloop, ' at: ', ctime()
if __name__ == '__main__':
seconds = [4, 8]
threads = []
print 'start main at: ', ctime()
# 创建线程,但是未启动
for i in range(len(seconds)):
thread = MyThread(loop, (i, seconds[i]), '')
threads.append(thread)
# 启动线程
for i in range(len(seconds)):
threads[i].start()
sleep(0.001)
for i in range(len(seconds)):
threads[i].join()
print 'end main at: ', ctime()
|
为了应用更加方便,将MyThread类独立抽象出来,后边直接引入就可以使用,并且保存下返回结果,做得更加通用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| from threading import Thread
from time import ctime
class MyThread(Thread):
def __init__(self, func, args, name):
Thread.__init__(self)
self.func = func
self.args = args
self.name = name
def getResult(self):
return self.res
def run(self):
print 'starting ', self.name, ' at: ', ctime()
self.func(*self.args)
print 'finish ', self.name , ' at: ', ctime()
|
为了展示多线程的实现结果,在每次计算的时候,sleep 0.1秒,并试图用多线程优化。
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
| from time import sleep
from time import ctime
from MyThread import MyThread
# 递归斐波那契
def recursion_fib(x):
sleep(0.1)
if x < 3:
return 1
else:
return recursion_fib(x-2) + recursion_fib(x-1)
# 递归阶乘
def recursion_multiply(x):
sleep(0.1)
if x < 2:
return 1
else:
return x*recursion_multiply(x-1)
# 递归实现时,只需要考虑这个函数在不同条件下的返回值,需要有终止条件
def recursion_sum(x):
sleep(0.1)
if x < 2:
return 1
else:
return x+recursion_sum(x-1)
if __name__ == '__main__':
func_names = ['fib', 'recMul', 'recSum']
funcs = [recursion_fib, recursion_multiply, recursion_sum]
threads = []
print 'start Single Thread -------------------'
for i in range(len(funcs)):
print 'start function ', func_names[i], ' at: ', ctime()
funcs[i](10)
print 'end function ', func_names[i], ' at: ', ctime()
print 'end Single Thread -------------------'
print 'start Multiple Thread -------------------'
for i in range(len(funcs)):
thread = MyThread(funcs[i], (10,), func_names[i])
threads.append(thread)
for i in range(len(funcs)):
threads[i].start()
sleep(0.001)
for i in range(len(funcs)):
threads[i].join()
print threads[i].getResult()
print 'end Multiple Thread -------------------'
|