进程是最小的资源单位,线程是最小的执行单位
一、进程
进程:就是一个程序在一个数据集上的一次动态执行过程。
进程由三部分组成:
1、程序:我们编写的程序用来描述进程要完成哪些功能以及如何完成
2、数据集:数据集则是程序在执行过程中所需要使用的资源
3、进程控制块:进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感
知进程存在的唯一标志。
二、线程
Threading用于提供线程相关的操作。线程是应用程序中工作的最小单元,它被包含在进程之中,是进程中的实际运作单位。一
条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
1、实现线程并发
示例1:
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 #Author: nulige
4
5 import threading #线程
6 import time
7
8 def Hi(num): #有一个参数
9 print("hello %s" %num)
10 time.sleep(3)
11
12 if __name__ == '__main__':
13
14 t1=threading.Thread(target=Hi,args=(10,)) #创建了一个线程对象t1,10做为一个参数,传给num
15 t1.start()
16
17 t2=threading.Thread(target=Hi,args=(9,)) #创建了一个线程对象t2,9做为一个参数,传给num
18 t2.start()
19
20 print("ending.........") #主线程输出ending
执行结果:
1 hello 10 #子线程
2 hello 9 #子线程
3 ending......... #主线程
4 #上面三个同时出来,再停顿三秒才结束
5 Process finished with exit code 0 #停顿3秒才结束
示例2:
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 #Author: nulige
4
5 import threading
6 import time
7
8 def music():
9 print("begin to listen %s"%time.ctime())
10 time.sleep(3)
11 print("stop to listen %s" %time.ctime())
12
13 def game():
14 print("begin to play game %s"%time.ctime())
15 time.sleep(5)
16 print("stop to play game %s" %time.ctime())
17
18 if __name__ == '__main__':
19
20 t1=threading.Thread(target=music)
21 t1.start()
22 t2=threading.Thread(target=game)
23 t2.start()
执行结果:
1 #总共花了5秒时间
2
3 begin to listen Sat Jan 14 12:34:43 2017
4 begin to play game Sat Jan 14 12:34:43 2017 #1、先打印2个
5
6 stop to listen Sat Jan 14 12:34:46 2017 #2、等待3秒再打印一个
7
8 stop to play game Sat Jan 14 12:34:48 2017 #3、再等待2秒,打印一个
2、使用join方法
示例1:
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 #Author: nulige
4
5 import threading
6 import time
7
8 def music():
9 print("begin to listen %s"%time.ctime())
10 time.sleep(3)
11 print("stop to listen %s" %time.ctime())
12
13 def game():
14 print("begin to play game %s"%time.ctime())
15 time.sleep(5)
16 print("stop to play game %s" %time.ctime())
17
18 if __name__ == '__main__':
19
20 t1=threading.Thread(target=music)
21 t2=threading.Thread(target=game)
22
23 t1.start() #运行实例的方法
24 t2.start()
25
26 t1.join() #子线程对象调用join()方法
27 t2.join()
28
29 print("ending") #在主线程中
执行结果:
begin to listen Sat Jan 14 12:58:34 2017
begin to play game Sat Jan 14 12:58:34 2017 #先打印2个
stop to listen Sat Jan 14 12:58:37 2017 #等待3秒,再打印一个
stop to play game Sat Jan 14 12:58:39 2017 #等待2秒,再打印两个
ending
示例2:
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 #Author: nulige
4
5 import threading
6 import time
7
8 def music():
9 print("begin to listen %s"%time.ctime())
10 time.sleep(3)
11 print("stop to listen %s" %time.ctime())
12
13 def game():
14 print("begin to play game %s"%time.ctime())
15 time.sleep(5)
16 print("stop to play game %s" %time.ctime())
17
18 if __name__ == '__main__':
19
20 t1=threading.Thread(target=music)
21 t2=threading.Thread(target=game)
22
23 t1.start() #运行实例的方法
24 t2.start()
25
26 t1.join() #t1线程不结束,谁都不往下走
27
28 print("ending")
执行结果:
1 begin to listen Sat Jan 14 13:06:07 2017
2 begin to play game Sat Jan 14 13:06:07 2017 #先打印这两行
3
4 stop to listen Sat Jan 14 13:06:10 2017 #再等待3秒打印这两行
5 ending
6
7 stop to play game Sat Jan 14 13:06:12 2017 #再等待2秒打印这行
示例3:
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 #Author: nulige
4
5 import threading
6 import time
7
8 def music():
9 print("begin to listen %s"%time.ctime())
10 time.sleep(3)
11 print("stop to listen %s" %time.ctime())
12
13 def game():
14 print("begin to play game %s"%time.ctime())
15 time.sleep(5)
16 print("stop to play game %s" %time.ctime())
17
18 if __name__ == '__main__':
19
20 t1=threading.Thread(target=music)
21 t2=threading.Thread(target=game)
22
23 t1.start() #运行实例的方法
24 t2.start()
25
26 t2.join()
27
28 print("ending") #在主线程中
执行结果:
1 begin to listen Sat Jan 14 13:12:34 2017 #先打印这两行
2 begin to play game Sat Jan 14 13:12:34 2017
3
4 stop to listen Sat Jan 14 13:12:37 2017 #等待3秒,打印这一行
5
6 stop to play game Sat Jan 14 13:12:39 2017 #等待2秒,打印这两行
7 ending
示例4:并没有实现并发(失去多线程的意义)
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 #Author: nulige
4
5 import threading
6 import time
7
8 def music():
9 print("begin to listen %s"%time.ctime())
10 time.sleep(3)
11 print("stop to listen %s" %time.ctime())
12
13 def game():
14 print("begin to play game %s"%time.ctime())
15 time.sleep(5)
16 print("stop to play game %s" %time.ctime())
17
18 if __name__ == '__main__':
19
20 t1=threading.Thread(target=music)
21 t2=threading.Thread(target=game)
22
23 t1.start()
24
25 t1.join()
26 t2.start()
27
28 t2.join()
29
30 print("ending") #在主线程中
执行结果:
1 begin to listen Sat Jan 14 13:26:18 2017 #先打印条1行
2
3 stop to listen Sat Jan 14 13:26:21 2017 #等待3秒再打印2行
4 begin to play game Sat Jan 14 13:26:21 2017
5
6 stop to play game Sat Jan 14 13:26:26 2017 #等待5秒打印2行
7 ending
三、线程的两种调用方式
threading 模块建立在 thread 模块之上。thread 模块以低级、原始的方式来处理和控制线程,而 threading 模块通过对 thread
进行二次封装,提供了更方便的 api 来处理线程。
1、直接调用(推荐写法)
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 #Author: nulige
4
5 import threading
6 import time
7
8
9 def sayhi(num): # 定义每个线程要运行的函数
10
11 print("running on number:%s" % num)
12
13 time.sleep(3)
14
15
16 if __name__ == '__main__':
17 t1 = threading.Thread(target=sayhi, args=(1,)) # 生成一个线程实例
18 t2 = threading.Thread(target=sayhi, args=(2,)) # 生成另一个线程实例
19
20 t1.start() # 启动线程
21 t2.start() # 启动另一个线程
22
23 print(t1.getName()) # 获取线程名
24 print(t2.getName())
执行结果:
1 running on number:1
2 running on number:2
3 Thread-1
4 Thread-2
2、继承式调用(有些编程人员会用这种写法,也要能看懂。不推荐这种写法)
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 #Author: nulige
4
5 import threading
6 import time
7
8 #自己定制一个MyThread的类
9 class MyThread(threading.Thread):
10 def __init__(self, num):
11 threading.Thread.__init__(self)
12 self.num = num
13
14 def run(self): # 定义每个线程要运行的函数
15
16 print("running on number:%s" % self.num)
17
18 time.sleep(3)
19
20
21 if __name__ == '__main__':
22 t1 = MyThread(1) #继承这个类,把1这个参数,传给num ,t1就是个线程对象
23 t2 = MyThread(2)
24 t1.start()
25 t2.start()
26
27 print("ending......")
执行结果:
1 running on number:1
2 running on number:2
3 ending......
四、 threading.thread的实例方法
1、join&Daemon方法
示例1:没有用Daemon方法示例
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 #Author: nulige
4
5 import threading
6 from time import ctime,sleep
7 import time
8
9 def ListenMusic(name):
10
11 print ("Begin listening to %s. %s" %(name,ctime()))
12 sleep(3)
13 print("end listening %s"%ctime())
14
15 def RecordBlog(title):
16
17 print ("Begin recording the %s! %s" %(title,ctime()))
18 sleep(5)
19 print('end recording %s'%ctime())
20
21 #创建一个列表,把t1和t2加到列表中去
22 threads = []
23 t1 = threading.Thread(target=ListenMusic,args=('水手',))
24 t2 = threading.Thread(target=RecordBlog,args=('python线程',))
25 threads.append(t1)
26 threads.append(t2)
27
28 if __name__ == '__main__':
29
30 for t in threads:
31 t.start()
32
33 print ("all over %s" %ctime())
执行结果:
1 Begin listening to 水手. Sat Jan 14 13:44:10 2017
2 Begin recording the python线程! Sat Jan 14 13:44:10 2017
3 all over Sat Jan 14 13:44:10 2017 #先打印三个出来; 主线程结束了
4
5 end listening Sat Jan 14 13:44:13 2017 #等待3秒,打印这1个; 子线程还没有结束,会继续往下运行
6
7 end recording Sat Jan 14 13:44:15 2017 #再等待2秒,打印这1个
示例2: 用Daemon方法示例(设置t为守护线程,就是子线程,跟着主线程一起退出)
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 #Author: nulige
4
5 import threading
6 from time import ctime,sleep
7 import time
8
9 def ListenMusic(name):
10
11 print ("Begin listening to %s. %s" %(name,ctime()))
12 sleep(3)
13 print("end listening %s"%ctime())
14
15 def RecordBlog(title):
16
17 print ("Begin recording the %s! %s" %(title,ctime()))
18 sleep(5)
19 print('end recording %s'%ctime())
20
21 #创建一个列表,把t1和t2加到列表中去
22 threads = []
23 t1 = threading.Thread(target=ListenMusic,args=('水手',))
24 t2 = threading.Thread(target=RecordBlog,args=('python线程',))
25 threads.append(t1)
26 threads.append(t2)
27
28 if __name__ == '__main__':
29
30 for t in threads:
31 t.setDaemon(True) #设置t为守护线程; 注意:一定在start()之前设置,否则会报错
32
33 t.start()
34
35 print ("all over %s" %ctime())
执行结果:
1 Begin listening to 水手. Sat Jan 14 13:51:30 2017 #三个同时打印出来
2 Begin recording the python线程! Sat Jan 14 13:51:30 2017
3 all over Sat Jan 14 13:51:30 2017
示例3:设置t1为守护线程,没有意义,达不到效果,因为t2还会继续执行
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 #Author: nulige
4
5 import threading
6 from time import ctime,sleep
7 import time
8
9 def ListenMusic(name):
10
11 print ("Begin listening to %s. %s" %(name,ctime()))
12 sleep(3)
13 print("end listening %s"%ctime())
14
15 def RecordBlog(title):
16
17 print ("Begin recording the %s! %s" %(title,ctime()))
18 sleep(5)
19 print('end recording %s'%ctime())
20
21 #创建一个列表,把t1和t2加到列表中去
22 threads = []
23 t1 = threading.Thread(target=ListenMusic,args=('水手',))
24 t2 = threading.Thread(target=RecordBlog,args=('python线程',))
25 threads.append(t1)
26 threads.append(t2)
27
28 if __name__ == '__main__':
29
30 t1.setDaemon(True) #设置t1为守护线程; 注意:一定在start之前设置,否则会报错
31 for t in threads:
32
33 t.start()
34
35 print ("all over %s" %ctime())
执行结果:
1 Begin listening to 水手. Sat Jan 14 14:02:07 2017
2 Begin recording the python线程! Sat Jan 14 14:02:07 2017
3 all over Sat Jan 14 14:02:07 2017 #设置t1为守护线程,所以会先把这三条先打印出来
4
5 end listening Sat Jan 14 14:02:10 2017 #再等待3秒打印t2,
6
7 end recording Sat Jan 14 14:02:12 2017 #再等待3秒打印这条出来
示例4:设置t2为守护线程,子线程才会跟着主线程一起退出
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 #Author: nulige
4
5 import threading
6 from time import ctime,sleep
7 import time
8
9 def ListenMusic(name):
10
11 print ("Begin listening to %s. %s" %(name,ctime()))
12 sleep(3)
13 print("end listening %s"%ctime())
14
15 def RecordBlog(title):
16
17 print ("Begin recording the %s! %s" %(title,ctime()))
18 sleep(5)
19 print('end recording %s'%ctime())
20
21 #创建一个列表,把t1和t2加到列表中去
22 threads = []
23 t1 = threading.Thread(target=ListenMusic,args=('水手',))
24 t2 = threading.Thread(target=RecordBlog,args=('python线程',))
25 threads.append(t1)
26 threads.append(t2)
27
28 if __name__ == '__main__':
29
30 t2.setDaemon(True) # 设置t2为守护线程; 注意:一定在start之前设置,否则会报错
31 for t in threads:
32
33 t.start()
34
35 print ("all over %s" %ctime())
执行结果:
1 Begin listening to 水手. Sat Jan 14 14:17:09 2017
2 Begin recording the python线程! Sat Jan 14 14:17:09 2017
3 all over Sat Jan 14 14:17:09 2017 #先打印这三条
4
5 end listening Sat Jan 14 14:17:12 2017 #等待3秒,再打印这条;t1结束后,主线程也结束了。
2、一道面试题
1 #执行结果是什么? 2 3 i = 0 4 for i in range(10): 5 i += 1 6 print(i) 7 8 执行结果: 9 10
3、其它方法
示例:getName()方法 (一般没什么用)
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 #Author: nulige
4
5 import threading
6 from time import ctime,sleep
7 import time
8
9 def ListenMusic(name):
10
11 print ("Begin listening to %s. %s" %(name,ctime()))
12 sleep(3)
13 print("end listening %s"%ctime())
14
15 def RecordBlog(title):
16
17 print ("Begin recording the %s! %s" %(title,ctime()))
18 sleep(5)
19 print('end recording %s'%ctime())
20
21 #创建一个列表,把t1和t2加到列表中去
22 threads = []
23 t1 = threading.Thread(target=ListenMusic,args=('水手',))
24 t2 = threading.Thread(target=RecordBlog,args=('python线程',))
25 threads.append(t1)
26 threads.append(t2)
27
28 if __name__ == '__main__':
29
30 t2.setDaemon(True) # 设置t为守护进程; 注意:一定在start之前设置,否则会报错
31 for t in threads:
32 t.start()
33 print(t.getName()) #返回线程名称:Thread-1
34
35 print ("all over %s" %ctime())
执行结果:
1 Begin listening to 水手. Sat Jan 14 14:36:44 2017
2 Thread-1 #返回线程名称
3 Begin recording the python线程! Sat Jan 14 14:36:44 2017
4 Thread-2 #返回默认的线程名称
5 all over Sat Jan 14 14:36:44 2017
6 end listening Sat Jan 14 14:36:47 2017
示例:threading.activeCount(),返回正在运行的线程数量
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 #Author: nulige
4
5 import threading
6 from time import ctime,sleep
7 import time
8
9 def ListenMusic(name):
10
11 print ("Begin listening to %s. %s" %(name,ctime()))
12 sleep(3)
13 print("end listening %s"%ctime())
14
15 def RecordBlog(title):
16
17 print ("Begin recording the %s! %s" %(title,ctime()))
18 sleep(5)
19 print('end recording %s'%ctime())
20
21 #创建一个列表,把t1和t2加到列表中去
22 threads = []
23 t1 = threading.Thread(target=ListenMusic,args=('水手',))
24 t2 = threading.Thread(target=RecordBlog,args=('python线程',))
25 threads.append(t1)
26 threads.append(t2)
27
28 if __name__ == '__main__':
29
30 t2.setDaemon(True) #设置t为守护进程; 注意:一定在start之前设置,否则会报错
31 for t in threads:
32 t.start()
33
34 print("count:", threading.active_count()) #判断有多少个线程的数量
35
36 while threading.active_count()==1: #等于1就相当于只有一个主线程,没有子线程
37
38 print ("all over %s" %ctime())
执行结果:
1 Begin listening to 水手. Sat Jan 14 14:49:00 2017
2 count: 2
3 Begin recording the python线程! Sat Jan 14 14:49:00 2017
4 count: 3 #得到的线程数量
5 end listening Sat Jan 14 14:49:03 2017
五、进程与线程的关系区别
六、python的GIL
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 havegrown
to depend on the guarantees that it enforces.)
上面的核心意思就是:无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行。
常见概念:
1、什么是并发和并行?
并发:是指系统具有处理多个任务(动作)的能力(CPU通过切换来完成并发),并发是并行的一个子集。
并行:是指系统具有同时处理多个任务(动作)的能力
2、同步与异步的区别?
同步: 当进程执行到一个IO(等待外部数据)的时候你---->会一直等:同步 (示例: 打电话)
异步:当进程执行到一个IO(等待外部数据)的时候你---->不等:一直等到数据接收成功,再回来处理。异步效率更高(示例:发短信)
3、任务分为:
1、对于IO密集型的任务: python的多线程是有意义的,可以采用:多进程+协程的方式
2、对于计算密集型的任务:python的多线程就不推荐。python就不适用了。
七、同步锁
示例1:不加锁(拿到的值是不固定的)
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 #Author: nulige
4
5 import threading
6 import time
7
8 def sub():
9 global num
10
11 # num -= 1
12 temp=num
13 time.sleep(0.001) #别外75个线程,拿到100了,时间不固定。
14 num=temp-1
15
16 num =100
17
18 l=[]
19
20 for i in range(100):
21 t=threading.Thread(target=sub)
22 t.start()
23 l.append(t)
24
25 for t in l:
26 t.join()
27
28 print(num)
执行结果:
1 73 or 75 (这个值是随机的,会不断变化)
示例2:加锁 (加锁的作用:就是把多线程变成串行,结果不会变)
1 #加锁的作用:就是把多线程变成串行,结果就不会变)
2
3 #!/usr/bin/env python
4 # -*- coding:utf-8 -*-
5 #Author: nulige
6
7 import threading
8 import time
9
10 def sub():
11
12 global num
13
14 # num -= 1
15 lock.acquire() #获取锁
16 temp=num
17 time.sleep(0.001)
18 num=temp-1
19 lock.release() #释放锁
20
21 num =100
22
23 l=[]
24 lock=threading.Lock()
25
26 for i in range(100):
27 t=threading.Thread(target=sub)
28 t.start()
29 l.append(t)
30
31 for t in l:
32 t.join()
33
34 print (num)
执行结果:
1 0
GIL:全局解释器锁
作用:保证同一时刻,只有一个线程被CPU执行,无论你有多少个线程。
为什么这里还需要lock? 注意啦,这里的lock是用户级的lock,跟那个GIL没关系 ,具体我们通过下图进行讲解
既然用户程序已经自己有锁了,那为什么C python还需要GIL呢?加入GIL主要的原因是为了降低程序的开发的复杂度,比如现在的你写python不需要关心内存回收的问题,因为Python解释器帮你自动定期进行内存回收,你可以理解为python解释器里有一个独立的线程,每过一段时间它起wake up做一次全局轮询看看哪些内存数据是可以被清空的,此时你自己的程序 里的线程和 py解释器自己的线程是并发运行的,假设你的线程删除了一个变量,py解释器的垃圾回收线程在清空这个变量的过程中的clearing时刻,可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了,结果就有可能新赋值的数据被删除了,为了解决类似的问题,python解释器简单粗暴的加了锁,即当一个线程运行时,其它人都不能动,这样就解决了上述的问题,这可以说是Python早期版本的遗留问题。
八、线程死锁和递归锁
在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都
正在使用,所有这两个线程在无外力作用下将一直等待下去。
示例1:线程死锁
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 # Author: nulige
4
5 import threading
6 import time
7
8
9 class MyThread(threading.Thread):
10 def actionA(self):
11 A.acquire() # count=1
12 print(self.name, "gotA", time.ctime())
13 time.sleep(2)
14
15 B.acquire() # count=2
16 print(self.name, "gotB", time.ctime())
17 time.sleep(1)
18
19 B.release() # count=1
20 A.release() # count=0
21
22 def actionB(self):
23 B.acquire() # count=1
24 print(self.name, "gotB", time.ctime())
25 time.sleep(2)
26
27 A.acquire() # count=2
28 print(self.name, "gotA", time.ctime())
29 time.sleep(1)
30
31 A.release() # count=1
32 B.release() # count=0
33
34 def run(self):
35 self.actionA()
36 self.actionB()
37
38
39 if __name__ == '__main__':
40
41 A = threading.Lock()
42 B = threading.Lock()
43
44 L = []
45
46 for i in range(5):
47 t = MyThread()
48 t.start()
49 L.append(t)
50
51 for i in L:
52 i.join()
53
54 print("ending.....")
执行结果:
1 Thread-1 gotA Mon Jan 16 17:33:58 2017
2 Thread-1 gotB Mon Jan 16 17:34:00 2017
3 Thread-1 gotB Mon Jan 16 17:34:01 2017
4 Thread-2 gotA Mon Jan 16 17:34:01 2017 #死锁,一直卡在这里
解决办法:
使用递归锁,将
|
1
2
|
lockA=threading.Lock()
lockB=threading.Lock()<br>#--------------<br>lock=threading.RLock()
|
为了支持在同一线程中多次请求同一资源,python提供了“可重入锁”:threading.RLock。RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源。
示例2:递归锁(解决死锁问题)
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 #Author: nulige
4
5 import threading
6 import time
7
8 class MyThread(threading.Thread):
9
10 def actionA(self):
11
12 r_lcok.acquire() #count=1
13 print(self.name,"gotA",time.ctime())
14 time.sleep(2)
15
16 r_lcok.acquire() #count=2
17 print(self.name,"gotB",time.ctime())
18 time.sleep(1)
19
20 r_lcok.release() #count=1
21 r_lcok.release() #count=0
22
23
24 def actionB(self):
25
26 r_lcok.acquire() #count=1
27 print(self.name,"gotB",time.ctime())
28 time.sleep(2)
29
30 r_lcok.acquire() #count=2
31 print(self.name,"gotA",time.ctime())
32 time.sleep(1)
33
34 r_lcok.release() #count=1
35 r_lcok.release() #count=0
36
37 def run(self):
38
39 self.actionA()
40 self.actionB()
41
42 if __name__ == '__main__':
43
44 r_lcok=threading.RLock()
45 L=[]
46
47 for i in range(5):
48 t=MyThread()
49 t.start()
50 L.append(t)
51
52 for i in L:
53 i.join()
54
55 print("ending.....")
执行结果:
1 Thread-1 gotA Mon Jan 16 17:38:42 2017
2 Thread-1 gotB Mon Jan 16 17:38:44 2017
3 Thread-1 gotB Mon Jan 16 17:38:45 2017
4 Thread-1 gotA Mon Jan 16 17:38:47 2017
5 Thread-3 gotA Mon Jan 16 17:38:48 2017
6 Thread-3 gotB Mon Jan 16 17:38:50 2017
7 Thread-4 gotA Mon Jan 16 17:38:51 2017
8 Thread-4 gotB Mon Jan 16 17:38:53 2017
9 Thread-5 gotA Mon Jan 16 17:38:54 2017
10 Thread-5 gotB Mon Jan 16 17:38:56 2017
11 Thread-5 gotB Mon Jan 16 17:38:57 2017
12 Thread-5 gotA Mon Jan 16 17:38:59 2017
13 Thread-3 gotB Mon Jan 16 17:39:00 2017
14 Thread-3 gotA Mon Jan 16 17:39:02 2017
15 Thread-4 gotB Mon Jan 16 17:39:03 2017
16 Thread-4 gotA Mon Jan 16 17:39:05 2017
17 Thread-2 gotA Mon Jan 16 17:39:06 2017
18 Thread-2 gotB Mon Jan 16 17:39:08 2017
19 Thread-2 gotB Mon Jan 16 17:39:09 2017
20 Thread-2 gotA Mon Jan 16 17:39:11 2017
21 ending.....