复习进程知识:
python:主进程,至少有一个主线程
启动一个新的子进程:Process,pool
给每一个进程设定一下执行的任务:传一个函数+函数的参数
如果是进程池:map函数:传入一个任务函数+一个序列
启动:start
多进程执行的时候:如果主进程退出了,子进程还在执行
如何让主进程等待子进程执行完毕再退出:调用join函数
多进程之间共享变量:跨进程的锁Manager.lock()
共享证书:Manager.Value(‘i’,0)
共享列表:
共享字典:
共享字符串“
3个多进程的框架
什么是线程?
线程是共享父进程的资源和空间
Ø线程是一个进程的实体,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程是由表示程序运行状态的寄存器(如程序计数器-就是表示要执行的指令是哪一条、栈指针-说明栈在哪儿)以及堆栈组成,它是比进程更小的单位。
Ø线程是程序中的一个执行流。一个执行流是由CPU运行程序代码并操作程序的数据所形成的。因此,线程被认为是以CPU为主体的行为。
Ø线程不包含进程地址空间中的代码和数据(这些在进程中把冷汗),线程是计算过程在某一时刻的状态。所以,系统在产生一个线程或各个线程之间切换时,负担要比进程小得多。
Ø线程是一个用户级的实体,线程结构驻留在用户空间中,能够被普通的用户级函数直接访问。
Ø一个线程本身不是程序,它必须运行于一个程序(进程)之中。因此,线程可以定义为一个程序中的单个执行流。
•多线程是指一个程序中包含多个执行流,多线程是实现并发的一种有效手段。一个进程在其执行过程中,可以产生多个线程(也是执行函数,),形成多个执行流。每个执行流即每个线程也有它自身的产生、存在和消亡的过程。
•多线程程序设计的含义就是可以将程序任务分成几个并行的子任务。
线程和进程的区别
切换线程不用切换上下文,效率要比多进程要高,但是python中鼓励多用多进程,用多进程效率高,
grl全局解释锁,效率降低,独占,让线程收到很多影响,无法利用多个CPU,
如果有多个I/O等待的,用多线程也可以,但是多线程比多进程要简单一些
java里强调使用多线程,因为没有全局解释锁,因此效率比较高
进程是资源分配的最小单位,线程是程序执行的最小单位
Ø进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
Ø线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。
Ø但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
线程的状态图
线程阻塞通常是指一个线程在执行过程中暂停,以等待某个条件的触发。
线程状态总结
就绪不是立刻执行,等cpu调度-》运行,
跑的时候有三种状态:1等待,2同步多线程(同步阻塞-等别的线程操作之后才继续,同步是顺序的,异步是同时来),3其他阻塞(sleep,I/O),
Python线程
Python中一些线程模块:
lthread(低版本使用,已废弃)
lthreading
lMultiprocessing
这里我们只介绍threading模块。
Python线程threading模块
Threading模块提供的类:
Thread,Lock,Rlock,Condition,Semaphore,Event,Timer,local等。
threading模块提供的常用方法:
üthreading.currentThread(): 返回当前的线程变量。-返回线程对象
üthreading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。-返回正在运行running状态的list,list里是线程名字
üthreading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同的结果
len(threading.enumerate())有相同的结果
线程类(Thread)
thread模块是Python低版本中使用的,高版本中被threading代替了。threading模块提供了更方便的API来操作线程。
Thread是threading模块中最重要的类之一,可以使用它来创建线程。
该类创建线程有有两种方式:
1.直接创建threading.Thread类的对象,初始化时将可调用对象作为参数传入。
2.通过继承Thread类,重写它的run方法。
Thread类的构造方法:
__init__(self, group=None, target=None, name=None, args=(), kwargs(字典)=None, verbose=None)
参数说明:
•group:线程组,目前还没有实现,库引用中提示必须是None;
•target:要执行的方法;
•name:线程名;
•args/kwargs:要传入方法的参数。
Thread类拥有的方法
1)isAlive():返回线程是否在运行。正在运行指的是启动后,终止前。
2)getName(name):获取线程名。
3)setName(name):设置线程名。
4)isDaemon(bool):判断线程是否随主线程一起结束。
5)setDaemon(bool):设置是否为守护线程。初始值从创建该线程的线程继承而来,默认为False,当没有非守护线程仍在运行时,程序将终止。比如,主线程A中,创建了子线程B,并且在主线程A中调用了B.setDaemon(),意思是,把主线程A设置为守护线程,这时候,要是主线程A执行结束了,就不管子线程B是否完成,一并和主线程A退出。这就是setDaemon方法的含义,这基本和join是相反的。此外,还有个要特别注意,必须在start() 方法调用之前调用此方法,如果不设置为守护线程,程序有可能会被无限挂起。
6)start():启动线程。
7)join([timeout]):阻塞当前上下文环境的线程,直到调用此方法的线程终止或到达指定的等待时间timeout(可选参数)。即当前的线程要等调用join()这个方法的线程执行完,或者是达到规定的时间。比如,主线程A中,创建了子线程B,并且在主线程A中调用了B.join(),那么,主线程A会在调用的地方等待,直到子线程B完成操作后,才可以接着往下执行,那么在调用这个线程时可以使用被调用线程的join方法。
8)run():用于表示线程活动的方法,通常需要重写,编写代码实现做需要的功能。
run需要先继承,
创建threading.Thread线程
#encoding=utf-8
from threading import Thread
import time
def run(a = None, b = None) :
print a, b
time.sleep(1)
t = Thread(target = run, args = ("this is a", "thread"))
#此时线程是新建状态
print t.getName() #获得线程对象名称
print t.isAlive() #判断线程是否还活着,在start后,在执行完毕前调用#isAlive()才会返回True
t.start() #启动线程
t.join() #主线程等待子线程t执行结束
print "done!"
c:\Python27\Scripts>python task_test.py
Thread-1
False
this is a thread
done!
t = Thread(target = run, args= ("this is a", "thread"))
这句话只是创建了一个线程,并未执行这个线程,此时线程处于新建状态。
t.start()启动线程,此时线程仍未处于运行状态,只是处于准备状态。
重写的函数run(),根据我们的需要自定义,参数来自args元组。
注释掉join()
#encoding=utf-8
from threading import Thread
import time
def run(a = None, b = None) :
print a, b
time.sleep(1)
t = Thread(target = run, args = ("this is a", "thread"))
#此时线程是新建状态
print t.getName() #获得线程对象名称
print t.isAlive() #判断线程是否还活着,在start后,在执行完毕前调用isAlive()才会返回True
t.start() #启动线程
#t.join() #主线程等待子线程t执行结束
print "done!"
c:\Python27\Scripts>python task_test.py
Thread-1
False
this is adone!
thread
用继承的方式启动线程
使用 Threadingg模块创建线程,直接从 threading.Thread 继承,然后重写 __init__方法和 run 方
#encoding=utf-8
from threading import Thread
import time
class MyThread(Thread) :
def __init__(self, a) :
#调用父类的构造方法
super(MyThread, self).__init__()
self.a = a
#继承的方式中,要通过run函数运行要执行的任务
def run(self) :
time.sleep(self.a)
print "sleep :", self.a
t1 = MyThread(2)
t2 = MyThread(4)
t1.start()
t2.start()
t1.join()
t2.join()
c:\Python27\Scripts>python
task_test.py
sleep : 2
sleep : 4
dong!
多线程的执行是没有顺序的,本例主要是增加了较长的等待时间,才看起来有执行顺序。
用这个模板往里套就行了,
继承Thread类,新增 run
方法参数
注意:
继承Thread类的新类MyThread构造函数中必须要调用父类的构造方法,这样才能产生父类的构造函数中的参数,才能产生线程所需要的参数。新的类中如果需要别的参数,直接在其构造方法中添加即可。同时,新类中,在重写父类的run方法时,它默认是不带参数的,如果需要给他提供参数,需要在类的构造函数中指定,因为在线程执行过程中,run方法是线程自己去调用的,不用我们手动调用,所以没法直接给传递参数,只能在构造方法中设定好参数,然后在run方法中再去取这些参数。
#encoding=utf-8
import threading
import time
class timer(threading.Thread):
#The timer
class is derived from the class threading.Thread
def
__init__(self, num, interval):
threading.Thread.__init__(self)
self.thread_num = num
self.interval = interval
self.thread_stop = False #是否让进程会停
def run(self):
#Overwrite
run() method, put what you want the thread do here
while not
self.thread_stop:#怎么跳出,下边的stop就是
print
'Thread Object(%d), Time:%s\n' %(self.thread_num, time.ctime())
time.sleep(self.interval)
def
stop(self):
self.thread_stop = True#停止死循环
def test():
thread1 =
timer(1, 1)
thread2 =
timer(2, 2)
thread1.start() #执行类里面的run方法
thread2.start()
time.sleep(10)#对主线程有效,当前程序在主线程里
thread1.stop()#改了self.thread_stop变量,主线程调用
thread2.stop() #停止线程,主线程调用这两个线程的方法,
if __name__ == '__main__':
test()
c:\Python27\Scripts>python
task_test.py
Thread Object(1),
Time:Sat Apr 14 11:04:54 2018
Thread Object(2),
Time:Sat Apr 14 11:04:54 2018
Thread Object(1),
Time:Sat Apr 14 11:04:55 2018
Thread Object(2),
Time:Sat Apr 14 11:04:56 2018
Thread Object(1),
Time:Sat Apr 14 11:04:56 2018
Thread Object(1),
Time:Sat Apr 14 11:04:57 2018
Thread Object(1),
Time:Sat Apr 14 11:04:58 2018
Thread Object(2),
Time:Sat Apr 14 11:04:58 2018
Thread Object(1),
Time:Sat Apr 14 11:04:59 2018
Thread Object(2),
Time:Sat Apr 14 11:05:00 2018
Thread Object(1),
Time:Sat Apr 14 11:05:00 2018
Thread Object(1),
Time:Sat Apr 14 11:05:01 2018
Thread Object(2),
Time:Sat Apr 14 11:05:02 2018
Thread Object(1),
Time:Sat Apr 14 11:05:02 2018
Thread Object(1),
Time:Sat Apr 14 11:05:03 2018
利用多核cpu,要用多进程,例如8个cpu,进行小数的计算,用8个线程去做,只有一个线程在做,另外7个线程空闲着,效率低,多进程可以把所有cpu都利用起来
线程退出
下面的程序实现了多线执行后自动退出 ,无需设定结束的时间也没有使用
join join 函数 ,主线程会自己退出。
#encoding=utf-8
import thread
import threading
import time
exitFlag = 0 #是否每个线程要进行工作后再退出,设定1则所有线程启动后直接退出
class myThread (threading.Thread):
#继承父类threading.Thread
def
__init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID #自己生成的
self.name = name
self.counter = counter
def run(self):
#把要执行的代码写到run函数里面线程在创建后会直接运行run函数
print
"Starting " + self.name
print_time(self.name,5, self.counter)
print
"Exiting " + self.name
def print_time(threadName, delay, counter):
while counter:
#
if
exitFlag:#默认是0,改成1才能退出
thread.exit()
time.sleep(delay)
print
"%s: %s" % (threadName, time.ctime(time.time()))
counter -=
1
# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
# 开启线程
thread1.start()
thread2.start()
print "Exiting Main Thread"
c:\Python27\Scripts>python
task_test.py
Starting
Thread-1Starting Thread-2Exiting Main Thread
Thread-1: Sat Apr
14 11:23:23 2018
Exiting
Thread-1Thread-2: Sat Apr 14 11:23:23 2018
Thread-2: Sat Apr
14 11:23:28 2018
Exiting Thread-2
修改:exitFlag
= 1
c:\Python27\Scripts>python task_test.py
Starting Thread-1
Exiting Main ThreadStarting Thread-2
Join()函数典型用法
在一个线程中,启动另外一个线程,执行后退出
解释:
主程序中tJoin=
threading.Thread(target=join)这句代码执行后,只是创建了一个线程对象tJoin,但并未启动线程。
tContext= threading.Thread(target=context, args=(tJoin,))
tContext.start()
上面这两句执行后,创建了一个线程对象tContext并启动该线程(打印in threadContext.),同时将tJoin线程对象作为参数传给context函数,在context函数中,启动了tJoin这个线程,同时该线程又调用了join()函数(tJoin.join()),那tContext线程将等待tJoin这线程执行完成后,才能继续tContext线程后面的,所以先执行join()函数,打印输出下面两句:
in threadJoin.
out threadJoin.
tJoin线程执行结束后,继续执行tContext线程,于是打印输出了out threadContext.,于是就看到我们上面看到的输出结果,并且无论执行多少次,结果都是这个顺序。但如果将context()函数中tJoin.join()这句注释掉,再执行该程序,打印输出的结果顺序就不定了,因为此时这两线程就是并发执行的。
示例代码:在一个线程中,启动另外执行后退出
看这个更好:
#coding=utf-8
import threading
import time
def context(tJoin):
print "in
threadContext"
tJoin.start()
tJoin.join()
print "out
threadContext"
def xjoin():
print "in
threadxJoin"
time.sleep(1)
print "out
threadxJoin"
tJoin=threading.Thread(target=xjoin)
tContext=threading.Thread(target=context,args=(tJoin,))
tContext.start()
c:\Python27\Scripts>python task_test.py
in threadContext
in threadxJoin
out threadxJoin
out threadContext
课件上的:
# encoding: UTF-8
import threading
import time
def context(tJoin):#把线程对象传给这个函数中来启动
print 'in
threadContext.'
tJoin.start()
# 将阻塞tContext直到threadJoin终止。
tJoin.join()
# tJoin终止后继续执行。
print 'out
threadContext.'
def join():
print 'in
threadJoin.'
time.sleep(1)
print 'out
threadJoin.'
tJoin = threading.Thread(target = join)
tContext = threading.Thread(target = context, args =
(tJoin,))
tContext.start()
c:\Python27\Scripts>python
task_test.py
in threadContext.
in threadJoin.
out threadJoin.
out threadContext.
# encoding: UTF-8
import threading
import time
def context(tJoin):
print 'in
threadContext.'
tJoin.start()
# 将阻塞tContext直到threadJoin终止。
tJoin.join()
# tJoin终止后继续执行。
print 'out
threadContext.'
def join1():
print 'in
threadJoin.'
time.sleep(1)
print 'out
threadJoin.'
tJoin = threading.Thread(target = join1)
tContext = threading.Thread(target = context, args =
(tJoin,))
tContext.start()
守护线程
没设置守护进程
# encoding: UTF-8
import threading
import time
class MyThread(threading.Thread):
def
__init__(self, id):
threading.Thread.__init__(self)
def
run(self):
time.sleep(5)
print
"This is " + self.getName()
if __name__ == "__main__":
t1 =
MyThread(999)
#t1.setDaemon(True) # 将子线程设置为守护线程
t1.start()
print "I am
the father thread."
c:\Python27\Scripts>python task_test.py
I am the father thread.
This is Thread-1
设为守护进程
# encoding: UTF-8
import threading
import time
class MyThread(threading.Thread):
def
__init__(self, id):
threading.Thread.__init__(self)
def
run(self):
time.sleep(5)
print
"This is " + self.getName()
if __name__ == "__main__":
t1 =
MyThread(999)
t1.setDaemon(True) # 将子线程设置为守护线程
t1.start()
print "I am
the father thread."
c:\Python27\Scripts>python
task_test.py
I am the father
thread.
主线程退出后,子线程也退出了,没打印后边的内容
从结果可以看出,调用了setDaemon()函数后,子线程被设置为守护线程,主线程打印内容后就结束了,不管子线程是否执行完毕,一并被终止了。可见join()函数与setDaemon()函数功能是相反的。
并发:
并分:一起运行,在一段时间内同时
并行:在同一秒都在运行
并行计算,多态服务器,同时对一个任务的分解,然后把每个任务做汇总
在操作系统中,指一个时间段内有几程序都处于已启动到运行结束之的状态,并且这几个程序都是在同一处理机上运行的但任时间点却只有个程序在处理机上执行。
注意并发与行不是同一个概念。指时间段内运,表示的是一个区间,而并行指在同时点上都运且发同一时间点上只能有个程序在运行。
在实际应用中,多个线程往会共享一些数据(如:内存堆栈、串口文件等),并且线程间的状态和行为都是互相影响。
并发线程的两种关系: 同步与互斥。
线程池(并发):
Python中线程multiprocessing模块与进程使用的同一模块。使用方法也基本相同,唯一不同的是,from
multiprocessing import Pool这样导入的Pool表示的是进程池;from multiprocessing.dummy
import Pool这样导入的Pool表示的是线程池。这样就可以实现线程并发了。
这里的pool.map()函数,跟进程池的map函数用法一样,也跟内建的map函数一样
#encoding=utf-8
import time
from multiprocessing.dummy import Pool as ThreadPool
#ThreadPool表示给线程池取一个别名ThreadPool
def run(fn):
time.sleep(2)
print fn
if __name__ == '__main__':
testFL =
[1,2,3,4,5]
pool =
ThreadPool(10)#创建10个容量的线程池并发执行
pool.map(run,
testFL)
pool.close()
pool.join()
c:\Python27\Scripts>python
task_test.py
34521
#coding=utf-8
import time
from multiprocessing.dummy import Pool as ThreadPool
def run(fn):
time.sleep(2)
print "%s
%s" %(fn,'\n')
if __name__ =='__main__':
testFL=[1,2,3,4,5]
pool=ThreadPool(10)
pool.map(run,testFL)
pool.close()
pool.join()
c:\Python27\Scripts>python task_test.py
5
3
4
2
线程同步和互斥,带锁
同步是按顺序
共享数据是并发执行得程序共享得
ü互斥:线程之间通过对资源的竞争,所产生的相互制约的关系,就是互斥关系。这类线程间主要的问题就是互斥和死锁的问题。
ü同步:进程之间不是相互排斥的关系,而是相互依赖的关系。换句话说,就是多进程共享同一临界资源时,前一个进程输出作为后一个进程的输入,当第一个进程没有输出时,第二个进程必须等待。因为当多个线程共享数据时,可能会导致数据处理出错,因此线程同步主要的目的就是使并发执行的各线程之间能够有效的共享资源和相互合作,从而使程序的执行具有可再现性。
ü共享数据指的是并发执行的多个线程间所操作的同一数据资源。
出现共享资源访问冲突的实质就是线程间没有互斥的使用共享资源,也就是说并发执行过程中,某一个线程正在对共享资源访问时,比如写,此时其它的线程就不能访问这个共享数据,直到正在访问它的线程访问结束。我们通过对共享资源进行加锁操作来避免访问冲突。当有线程拿到访问这个共享数据的权限时,就对其加一把锁,这样别的线程由于得不到访问的锁,所以不能访问,直到线程释放了这把锁,其它线程才能访问。线程的同步与互斥,是为了保证所共享的数据的一致性。
线程互斥
该实例创建了 3个线程 t1
、t2 和t3 同步
执行,三个线程都访问全局变量
data , 并改变它的值。当第一个线程 t1
请求锁
成功后,开始访问共享数据
data ,第二 个线程 t2
和t3 也开始请求锁,但是此时 t1
还没有释放锁,所以 t2
、t3 处于等待
锁状态,直到 t1
调用
lock.release () 释放锁以后, t2 才得到锁,然后执行完释
放锁, t3
才能得到锁。这
样就保证了三个线程共享数据
data 的一致性和同步 性。并且这三个线程是发执行的,没有人为控制其获得锁的顺序,所以它们执行的顺序也是不定。
注意:
调用 acquire([timeout]) 时,线程将一
直阻塞,到
获得锁或者timeout秒后返回是否获得锁。
#encoding=utf-8
import threading
import time
data = 0
lock = threading.Lock()#创建一个锁对象,线程锁
def func() :
global data
print "%s
acquire lock...\n" %threading.currentThread().getName()
if lock.acquire()
:
print "%s
get lock...\n" %threading.currentThread().getName()
data += 1 #must
lock
time.sleep(2)#其它操作
print "%s
release lock...\n" %threading.currentThread().getName()
#调用release()将释放锁
lock.release()
startTime = time.time()
t1 = threading.Thread(target = func)#三个线程调用同一个函数,只能有一个线程在打印
t2 = threading.Thread(target = func)
t3 = threading.Thread(target = func)
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
endTime = time.time()
print "used time is", endTime - startTime
c:\Python27\Scripts>python
task_test.py
Thread-1 acquire
lock...
Thread-2 acquire
lock...
Thread-1 get
lock...
Thread-3 acquire
lock...
Thread-1 release
lock...
Thread-2 get
lock...
Thread-2 release
lock...
Thread-3 get
lock...
Thread-3 release
lock...
used time is
6.01999998093
注释掉锁:
看时间:没锁的2秒多,有锁的6秒多
#encoding=utf-8
import threading
import time
data = 0
lock = threading.Lock()#创建一个锁对象,线程锁
def func() :
global data
print "%s
acquire lock...\n" %threading.currentThread().getName()
if 1:
print "%s
get lock...\n" %threading.currentThread().getName()
data += 1 #must
lock
time.sleep(2)#其它操作
print "%s
release lock...\n" %threading.currentThread().getName()
#调用release()将释放锁
#lock.release()
startTime = time.time()
t1 = threading.Thread(target = func)#三个线程调用同一个函数,只能有一个线程在打印
t2 = threading.Thread(target = func)
t3 = threading.Thread(target = func)
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
endTime = time.time()
print "used time is", endTime - startTime
c:\Python27\Scripts>python task_test.py
Thread-1 acquire lock...
Thread-2 acquire lock...
Thread-1 get lock...
Thread-3 acquire lock...
Thread-2 get lock...
Thread-3 get lock...
Thread-1 release lock...
Thread-2 release lock...
Thread-3 release lock...
used time is 2.01699995995
锁去了,是并行的过程
不带锁,三个线程循环执行100000次,可以看到读写冲突,每次执行的结果都不一样
#encoding=utf-8
import threading
import time
data = 0
lock = threading.Lock()#创建一个锁对象
def func() :
global data
print "%s
acquire lock...\n" %threading.currentThread().getName()
if 1 :
print "%s
get lock...\n" %threading.currentThread().getName()
for i in
range(100000):
data += 1
#must lock
#time.sleep(2)#其它操作
print "%s
release lock...\n" %threading.currentThread().getName()
#调用release()将释放锁
#lock.release()
startTime = time.time()
t1 = threading.Thread(target = func)
t2 = threading.Thread(target = func)
t3 = threading.Thread(target = func)
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
print data
endTime = time.time()
print "used time is", endTime - startTime
c:\Python27\Scripts>python task_test.py
Thread-1 acquire lock...
Thread-2 acquire lock...
Thread-1 get lock...
Thread-3 acquire lock...
Thread-2 get lock...
Thread-3 get lock...
Thread-1 release lock...
Thread-2 release lock...
Thread-3 release lock...
193625
used time is 0.114000082016
c:\Python27\Scripts>python task_test.py
Thread-1 acquire lock...
Thread-2 acquire lock...
Thread-1 get lock...
Thread-3 acquire lock...
Thread-2 get lock...
Thread-3 get lock...
Thread-1 release lock...
Thread-2 release lock...
Thread-3 release lock...
150707
used time is 0.119999885559
带锁的话,结果就不会变了
#encoding=utf-8
import threading
import time
data = 0
lock = threading.Lock()#创建一个锁对象
def func() :
global data
print "%s
acquire lock...\n" %threading.currentThread().getName()
if
lock.acquire():
print "%s
get lock...\n" %threading.currentThread().getName()
for i in
range(100000):
data += 1
#must lock
#time.sleep(2)#其它操作
print "%s
release lock...\n" %threading.currentThread().getName()
#调用release()将释放锁
lock.release()
startTime = time.time()
t1 = threading.Thread(target = func)
t2 = threading.Thread(target = func)
t3 = threading.Thread(target = func)
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
print data
endTime = time.time()
print "used time is", endTime - startTime
c:\Python27\Scripts>python task_test.py
Thread-1 acquire lock...
Thread-2 acquire lock...
Thread-1 get lock...
Thread-3 acquire lock...
Thread-1 release lock...
Thread-2 get lock...
Thread-2 release lock...
Thread-3 get lock...
Thread-3 release lock...
300000
used time is 0.0510001182556
结果分析:
由带锁和不带锁结果分析,循环1000000次,执行data+1的动作,不带锁时,平均执行结果为1500000左右,此种场景中,三个线程并行、不受限制的各自循环1000000次让data加1,冲突的读写情况可能是:假如某个线程取去data的值为5000,执行加1动作,然后写入5001,此时另一个线程也同时取出data值为5000,执行加1的动作,然后也写入5001,这样导致两个线程执行的这个动作冲突,从结果上看,一个线程的读取动作并没有真正的使data加1,此种冲突情况多次发生,最后导致结果的不正确,但是从规律来看,三个线程并行对data进行读取1000000次,平均失效的概率是循环次数的一半
带锁时,对data进行读取的动作不存在并行操作,加锁使程序在某一时间点只能有一个线程进行读取,一个线程在执行读取操作时获得线程锁,别的线程无法进行操作,这就保证了结果的正确性,保证每次循环的读取操作没有冲突,每次操作都真正的使data加1了
线程同步-生产者、消费者(队列)
#coding=utf-8
from Queue import Queue #队列类
import random
import threading
import time
#生成者线程
class Producer(threading.Thread): #继承线程父类
def
__init__(self, t_name, queue):
#调用父线程的构造方法。
threading.Thread.__init__(self, name = t_name)
self.data =
queue #放队列里
def
run(self):
for i in
range(5):#放5个数
print
"%s: %s is producing %d to the queue!\n" %(time.ctime(),
self.getName(), i)
self.data.put(i)#向队列中添加数据
#产生一个0-2之间的随机数进行睡眠
time.sleep(random.randrange(10) / 5)
print
"%s: %s finished!" %(time.ctime(), self.getName())
#消费者线程
class Consumer(threading.Thread):
def
__init__(self, t_name, queue):
threading.Thread.__init__(self, name = t_name)
self.data =
queue
def
run(self):
for i in
range(5): #取5次,如果是6次,不会报错,会挂起,取不到的话会等,可以设置时间
val =
self.data.get()#从队列中取出数据,没有参数,死等
print
"%s: %s is consuming. %d in the queue is consumed!\n" %(time.ctime(),
self.getName(), val)
time.sleep(random.randrange(10))
print
"%s: %s finished!" %(time.ctime(), self.getName())
#Main thread
def main():#生成两个实例
queue =
Queue()#创建一个队列对象(特点先进先出)
producer =
Producer('Pro.', queue)#生产者对象
consumer =
Consumer('Con.', queue)#消费者对象
producer.start()#启动
consumer.start()
producer.join()#等待
consumer.join()
print 'All
threads terminate!' #结束
if __name__ == '__main__':
main()
c:\Python27\Scripts>python
task_test.py
Sat Apr 14
12:01:19 2018: Pro. is producing 0 to the queue!
Sat Apr 14
12:01:19 2018: Con. is consuming. 0 in the queue is consumed!
Sat Apr 14
12:01:20 2018: Pro. is producing 1 to the queue!
Sat Apr 14
12:01:20 2018: Pro. is producing 2 to the queue!
Sat Apr 14
12:01:21 2018: Pro. is producing 3 to the queue!
Sat Apr 14
12:01:21 2018: Pro. is producing 4 to the queue!
Sat Apr 14
12:01:21 2018: Pro. finished!
Sat Apr 14
12:01:26 2018: Con. is consuming. 1 in the queue is consumed!
Sat Apr 14
12:01:26 2018: Con. is consuming. 2 in the queue is consumed!
Sat Apr 14
12:01:32 2018: Con. is consuming. 3 in the queue is consumed!
Sat Apr 14
12:01:40 2018: Con. is consuming. 4 in the queue is consumed!
Sat Apr 14
12:01:40 2018: Con. finished!
All threads
terminate!
如果存了5次,取了6次,那会挂起,死住了
for i in range(5):这里改成6次,然后get(),会死等,挂起
线程同步-加锁Lock
#encoding=utf-8
from threading import Thread, Lock
import threading
def run(lock, num):
lock.acquire() # 获得锁
# 取得当前线程的线程名
threadName =
threading.current_thread().getName()
print "%s,
Hello Num: %s" %(threadName, num)
lock.release() # 释放锁
if __name__ == '__main__':
lock =
Lock() # 创建一个共享锁实例
for num in
range(20):
Thread(name =
'Thread-%s' %str(num), target = run, args = (lock, num)).start()
c:\Python27\Scripts>python
task_test.py
Thread-0, Hello
Num: 0
Thread-1, Hello
Num: 1
Thread-2, Hello
Num: 2
Thread-3, Hello
Num: 3
Thread-4, Hello
Num: 4
Thread-5, Hello
Num: 5
Thread-6, Hello
Num: 6
Thread-7, Hello
Num: 7
Thread-8, Hello
Num: 8
Thread-9, Hello
Num: 9
Thread-10, Hello
Num: 10
Thread-11, Hello
Num: 11
Thread-12, Hello
Num: 12
Thread-13, Hello
Num: 13
Thread-14, Hello Num:
14
Thread-15, Hello
Num: 15
Thread-16, Hello
Num: 16
Thread-17, Hello
Num: 17
Thread-18, Hello
Num: 18
Thread-19, Hello
Num: 19
获取线程名字的两种方法
>>> threading.current_thread().name
'MainThread'
>>> threading.current_thread().getName()
'MainThread'
线程同步semaphore,多个线程会获得多把锁
#encoding=utf-8
from threading import Thread, Lock
import threading
import time
def worker(s, i):
s.acquire()
print(threading.current_thread().name + " acquire")
time.sleep(i)
print(threading.current_thread().name + " release")
s.release()
if __name__ == "__main__":
# 设置限制最多3个线程同时访问共享资源
s =
threading.Semaphore(3)
for i in
range(5):
t =
Thread(target = worker, args = (s, i * 2))
t.start()
c:\Python27\Scripts>python
task_test.py
Thread-1 acquire
Thread-1 releaseThread-2 acquire
Thread-3 acquire
Thread-4 acquire
Thread-2 release
Thread-5 acquire
Thread-3 release
Thread-4 release
Thread-5 release
修改sleep()参数,效果明显一些
#encoding=utf-8
from threading import Thread, Lock
import threading
import time
def worker(s):
s.acquire()
print(threading.current_thread().name + " acquire")
time.sleep(5)
print(threading.current_thread().name + " release")
s.release()
if __name__ == "__main__":
# 设置限制最多3个线程同时访问共享资源
s =
threading.Semaphore(3)
for i in
range(5):
t =
Thread(target = worker, args = (s, ))
t.start()
c:\Python27\Scripts>python
task_test.py
Thread-1 acquire
Thread-2 acquire
Thread-3 acquire
Thread-1 release
Thread-4 acquire
Thread-2 release
Thread-5
acquireThread-3 release
Thread-4 release
Thread-5 release
线程同步-event信号传递
阻塞:等待某个条件发生,
挂起:就是死在那儿了,可能是永久性的
#encoding=utf-8
from threading import Thread, Lock
import threading
import time
def wait_for_event(e):
"""Wait for the event to be set before doing
anything"""
print
'wait_for_event: starting'
e.wait() # 等待收到能执行信号,如果一直未收到将一直阻塞
print
'wait_for_event: e.is_set()->', e.is_set()
def wait_for_event_timeout(e, t):
"""Wait t seconds and then timeout"""
print
'wait_for_event_timeout: starting'
e.wait(t)# 等待t秒超时,此时Event的状态仍未未设置
print
'wait_for_event_timeout: e.is_set()->', e.is_set()
e.set()# 设置Event的状态,等待有人调用e.set()
if __name__ == '__main__':
e =
threading.Event()
print
"begin, e.is_set()", e.is_set()
w1 =
Thread(name = 'block', target = wait_for_event, args = (e,))
w1.start()
w2 =
Thread(name = 'nonblock', target = wait_for_event_timeout, args = (e, 2))#等待有限时间
w2.start()#
print 'main:
waiting before calling Event.set()'
time.sleep(3)
#e.set()
print 'main: event is set'
c:\Python27\Scripts>python
task_test.py
begin, e.is_set()
False
wait_for_event:
starting
wait_for_event_timeout:
startingmain: waiting before calling Event.set()
wait_for_event_timeout:
e.is_set()-> False
wait_for_event:
e.is_set()-> True
main: event is set
做线程并发框架时,这些用的不多
线程同步-Condition
#encoding=utf-8
import threading as tr
import time
def consumer(cond):
with cond:
print("consumer before wait")
cond.wait() # 等待消费
print("consumer after wait")
def producer(cond):
with cond:
print("producer before notifyAll")
cond.notify_all() # 通知消费者可以消费了
print("producer after notifyAll")
if __name__ == '__main__':
condition =
tr.Condition()
t1 = tr.Thread(name
= "thread-1", target = consumer, args=(condition,))
t2 =
tr.Thread(name = "thread-2", target = consumer, args=(condition,))
t3 =
tr.Thread(name = "tjread-3", target = producer, args=(condition,))
t1.start()
time.sleep(2)
t2.start()
time.sleep(2)
t3.start()
c:\Python27\Scripts>python
task_test.py
consumer before
wait
consumer before
wait
producer before
notifyAll
producer after
notifyAll
consumer after
wait
consumer after
wait
线程同步,消息队列Queue
前边的函数对队列的操作是后边函数对队列操作的依赖
#encoding=utf-8
from threading import Thread
from Queue import Queue
import random, time
# 储钱罐
def create(queue): #得先让这个执行
for i in [100,
50, 20, 10, 5, 1, 0.5]:
if not
queue.full():
queue.put(i) # 入队列
print 'Put
%sRMB to queue.' %i
time.sleep(1)
# 取储钱罐中的零钱花
def get(queue):
while 1:
if not
queue.empty():
print 'Get
%sRMB from queue.' %queue.get()
time.sleep(2)
else:
break
q = Queue(5) # 创建一个队列实例
create_t = Thread(target = create, args = (q,))
get_t = Thread(target = get, args = (q,))
create_t.start()#先执行,存东西,最好后边sleep 1秒,比较稳妥
get_t.start()
create_t.join()
get_t.join()
c:\Python27\Scripts>python
task_test.py
Put 100RMB to
queue.
Get 100RMB from
queue.
Put 50RMB to
queue.
Put 20RMB to
queue.Get 50RMB from queue.
Put 10RMB to
queue.
Put 5RMB to
queue.Get 20RMB from queue.
Put 1RMB to queue.
Get 10RMB from
queue.
Put 0.5RMB to
queue.
Get 5RMB from
queue.
Get 1RMB from
queue.
Get 0.5RMB from
queue.
多线程怎么获取函数返回值?写文件,放数据库都行
死锁
如果程序中多个线程相互等待对方持有的锁,而在得到对方的锁之前都不释放自己的锁,由此导致了这些线程不能继续运行,这就是死锁。
死锁的表现是:程序死循环。
防止死锁一般的做法是:如果程序要访问多个共享数据,则首先要从全局考虑定义一个获得锁的顺序,并且在整个程序中都遵守这个顺序。释放锁时,按加锁的反序释放即可。
所以必须是有两个及其以上的的并发线程,才能出现死锁,如果是多于2个线程之间出现死锁,那他们请求锁的关系一定是形成了一个环,比如A等B的锁,B等C的锁,C等A的锁。
#coding=utf-8
import threading
import time
lock1 = threading.Lock()#全局变量
lock2 = threading.Lock()
print lock1, lock2
class T1(threading.Thread):
def
__init__(self, name):
threading.Thread.__init__(self)
self.t_name
= name
def run(self):
lock1.acquire()
time.sleep(1)#睡眠的目的是让线程2获得调度,得到第二把锁
print 'in
thread T1',self.t_name
time.sleep(2)
lock2.acquire() #线程1请求第二把锁,锁二已经被别人获取了
print 'in
lock l2 of T1'
lock2.release()
lock1.release()
class T2(threading.Thread):
def
__init__(self, name):
threading.Thread.__init__(self)
self.t_name
= name
def
run(self):
lock2.acquire()#先拿到
time.sleep(2)#睡眠的目的是让线程1获得调度,得到第一把锁
print 'in thread T2',self.t_name
lock1.acquire() #线程2请求第一把锁,锁1已经被别人获取了
print 'in
lock l1 of T2'
lock1.release()
lock2.release()
def test():
thread1 =
T1('A')
thread2 =
T2('B')
thread1.start()
thread2.start()
if __name__== '__main__':
test()
c:\Python27\Scripts>python
task_test.py
<thread.lock
object at 0x05738090><thread.lock object at 0x057380A0>
in thread T1 A
in thread T2 B
实例中,在两个线程thread1和thread2分别得到一把锁后,然后在线程1中请求线程2得到的那把锁,线程2中请求在线程1中得到的那把锁,由于两个线程都在请求对方的锁,但却没有一方释放它们的锁,所以就会出现死锁的情况,程序执行后就会出现下面程序一直等待的结果。
修改后,不死锁:
#coding=utf-8
import threading
import time
lock1 = threading.Lock()#全局变量
lock2 = threading.Lock()
print lock1, lock2
class T1(threading.Thread):
def
__init__(self, name):
threading.Thread.__init__(self)
self.t_name
= name
def
run(self):
lock1.acquire()
time.sleep(1)#睡眠的目的是让线程2获得调度,得到第二把锁
print 'in
thread T1',self.t_name
time.sleep(2)
#lock2.acquire() #线程1请求第二把锁,锁二已经被别人获取了
#lock2.acquire() #线程2请求第一把锁,锁1已经被别人获取了
print 'in
lock l2 of T1'
#lock2.release()
lock1.release()
class T2(threading.Thread):
def
__init__(self, name):
threading.Thread.__init__(self)
self.t_name
= name
def
run(self):
lock2.acquire()#先拿到
time.sleep(2)#睡眠的目的是让线程1获得调度,得到第一把锁
print 'in
thread T2',self.t_name
#lock1.acquire() #线程2请求第一把锁,锁1已经被别人获取了
print 'in lock l1 of T2'
#lock1.release()
lock2.release()
def test():
thread1 =
T1('A')
thread2 =
T2('B')
thread1.start()
thread2.start()
if __name__== '__main__':
test()
c:\Python27\Scripts>python
task_test.py
<thread.lock
object at 0x051B8090><thread.lock object at 0x051B80A0>
in thread T1 A
in thread T2 B
in lock l1 of T2
in lock l2 of T1
队列和进程池区别:
队列是一种数据结构,先进先出,一般用来多线程的同步,或顺序处理数据时,先存到内存队列里,然后在慢慢取数据,
写内存的速度比磁盘快10倍
进程池是在想创建多个进程时用的
怎么写内存泄漏
java内存泄漏:内存里的东西有用,不能回收,但是内存又不够用,就泄漏了
概念:短生命周期的对象被一个更长生命周期的对象持有,理论上导致段生命周期的对象在更长时间被回收了,会导致内存溢出
怎么让内存满了,不回收呢,
例子:
容器map,死循环往里写东西,内存就会爆了
写一个静态变量,一直往里拼
队列,一直往里塞
Python中的队列(Queue模块)
python queue模块有三种队列:
1、python queue模块的FIFO队列先进先出(Queue类)。
2、LIFO类似于堆。即先进后出(LifoQueue)。
3、还有一种是优先级队列级别越低越先出来(PriorityQueue)。
针对这三种队列分别有三个构造函数:
1、class Queue.Queue(maxsize) FIFO
2、class Queue.LifoQueue(maxsize) LIFO
3、class Queue.PriorityQueue(maxsize) 优先级队列
创建一个队列对象
python中有专门的队列类Queue,上面我们介绍的进程multiprocessing模块中自带有队列类,那里的队列类跟这里讲的队列类功能一样。
创建一个队列对象:
#coding=utf-8
import Queue
q = Queue.Queue(maxsize= 10)
Queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。
入队列
代码示例:
#coding=utf-8
import Queue
q = Queue.Queue(maxsize= 10)
q.put(10)
q.put('keys')
将值放入队列,调用的是队列对象的put()方法,将在队列尾插入一个项目,非阻塞。
put()方法的原型:
put( item, block=True, timeout=None)
参数说明:
item:表示要插入的项目的值,必须。
block:可选参数,默认为1。如果队列满了且block为1,put()方法就使调用线程暂停,直到空出一个数据单元。如果block为0,put方法将引发Full异常。
timeout:等待时间,可选。
代码示例:
#coding=utf-8
import Queue
q = Queue.Queue(maxsize = 10)
q.put(10)
q.put('keys')
print q.get()
print q.get()
出队列
执行结果:
从对列中取值,使用的是队列对象的get()方法,调用一次将从队列头删除一个项目,并返回该项目。
get()方法原型:
get(block=True, timeout=None)
参数说明:
block:可选参数,默认为True。如果队列为空且block为True,get()就使调用线程暂停,直至有项目可用。如果队列为空且block为False,队列将引发Empty异常。
timeout:等待时间。可选,默认为None。
>>>
import Queue
>>>
q=Queue.Queue()
>>> q
<Queue.Queue
instance at 0x027D3558>
>>>
q.get(block=False)
Traceback (most
recent call last):
File "<stdin>", line 1, in
<module>
File "C:\Python27\lib\Queue.py",
line 165, in get
raise Empty
Queue.Empty
队列中其他常用方法
üq.qsize()返回队列的大小
üq.empty()如果队列为空,返回True,否则False
üq.full()
如果队列满了,返回True,否则False
üq.full与maxsize
大小对应
üq.get_nowait()相当q.get(False)
üq.put_nowait(item)相当q.put(item,
False)
üq.task_done()在完成一项工作之后,q.task_done()
函数向任务已经完成的队列发送一个信号
üq.join()
实际上意味着等到队列为空,再执行别的操作。
队列实现多线程数据同步
逻辑:
#启动三个线程,每个线程调用process_data函数
#process_data函数的读取队列中的值
#队列一共放入5个单词,从one到five
#exitflag=0,表示线程继续运行,等于1的时候,表示
#所有队列值已经取出来了,所有线程停止。
#coding=utf-8
import Queue
import threading
import time
exitFlag = 0
class myThread (threading.Thread):
def
__init__(self, threadID, name, q):
threading.Thread.__init__(self)
self.threadID = threadID
self.name =
name
self.q = q#队列
def run(self):
print
"Starting " + self.name
process_data(self.name, self.q)#调用函数,处理数据
print
"Exiting " + self.name
def process_data(threadName, q):
while not
exitFlag:
queueLock.acquire()#取队列锁
if not
workQueue.empty():#从队列里取数据,
data =
q.get()
queueLock.release()
print
"%s processing %s" % (threadName, data)
else:
queueLock.release()
time.sleep(1)
threadList = ["Thread-1", "Thread-2",
"Thread-3"]#三个线程名字
nameList = ["One", "Two",
"Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = Queue.Queue(10)
threads = []#存放线程对象
threadID = 1
# 创建新线程
for tName in threadList:
thread = myThread(threadID,
tName, workQueue)#生成三个线程,
thread.start()#三个线程都启动
threads.append(thread) # 将线程加入线程列表
threadID += 1
# 填充队列
queueLock.acquire()
for word in nameList:
workQueue.put(word)#把nameList放到队列里
queueLock.release()
# 等待队列清空
while not workQueue.empty():
#可以加个sleep
pass
# 通知线程是时候退出
exitFlag = 1
# 等待所有线程完成
for t in threads:
t.join()#所有线程都退出完毕
print "Exiting Main Thread"
c:\Python27\Scripts>python
task_test.py
Starting Thread-1
Starting Thread-2
Starting Thread-3
Thread-3 processing
One
Thread-1
processing Two
Thread-3
processing ThreeThread-2 processing Four
Thread-2
processing Five
Exiting
Thread-3Exiting Thread-1
Exiting Thread-2
Exiting Main
Thread
python 线程、多线程的更多相关文章
-
python学习笔记-(十三)线程&;多线程
为了方便大家理解下面的知识,可以先看一篇文章:http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html 线程 1.什么是线程? ...
-
python——线程与多线程进阶
之前我们已经学会如何在代码块中创建新的线程去执行我们要同步执行的多个任务,但是线程的世界远不止如此.接下来,我们要介绍的是整个threading模块.threading基于Java的线程模型设计.锁( ...
-
python——线程与多线程基础
我们之前已经初步了解了进程.线程与协程的概念,现在就来看看python的线程.下面说的都是一个进程里的故事了,暂时忘记进程和协程,先来看一个进程中的线程和多线程.这篇博客将要讲一些单线程与多线程的基础 ...
-
Python的多线程(threading)与多进程(multiprocessing )
进程:程序的一次执行(程序载入内存,系统分配资源运行).每个进程有自己的内存空间,数据栈等,进程之间可以进行通讯,但是不能共享信息. 线程:所有的线程运行在同一个进程中,共享相同的运行环境.每个独立的 ...
-
Python GIL 多线程机制 (C source code)
最近阅读<Python源码剖析>对进程线程的封装解释: GIL,Global Interpreter Lock,对于python的多线程机制非常重要,其如何实现的?代码中实现如下: 指向一 ...
-
[python] 线程锁
参考:http://blog.csdn.net/kobeyan/article/details/44039831 1. 锁的概念 在python中,存在GIL,也就是全局解释器锁,能够保证同一时刻只有 ...
-
[python] 线程简介
参考:http://www.cnblogs.com/aylin/p/5601969.html 我是搬运工,特别感谢张岩林老师! python 线程与进程简介 进程与线程的历史 我们都知道计算机是由硬件 ...
-
Python 线程(threading) 进程(multiprocessing)
*:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...
-
Python实现多线程HTTP下载器
本文将介绍使用Python编写多线程HTTP下载器,并生成.exe可执行文件. 环境:windows/Linux + Python2.7.x 单线程 在介绍多线程之前首先介绍单线程.编写单线程的思路为 ...
随机推荐
-
CSS:CSS样式表及选择器优先级总结
我们在写网页的时候经常会遇到同一个HTML文件,使用了外部样式.内部样式以及内联样式,那么如果发生冲突时浏览器是怎么抉择的呢? 也会遇到这样的情况,在样式表中,对同一个HTML元素,我们有可能既用到了 ...
-
Hibernate中的组合映射
1.实体bean设计 car: public class Car { private int id; private String name; private Wheel wheel; set... ...
-
PHP 上传图片和安全处理
上传图片 public function images() { $data = $_FILES['file']; switch($data['type']) { case 'image/jpeg': ...
-
一个IT男的创业感言
2014年的一月 我和高中的一个同学開始人生的第一次创业.就写点第一次创业的经验吧! 创业的最初 须要心中大概有个规划 这个规划可能是非常粗糙的 但大的方向一定要有.详细就是你进入的行业 行业已经存在 ...
-
mvn打包发布
一:打包 cmd进入工作目录运行命令 1: mvn clean 2: mvn install 3: mvn clean compile 4: mvn package -DiskipTest ...
-
_WSAStartup@8,该符号在函数 _main 中被引用
int WSAStartup( __in WORD wVersionRequested, __out LPWSADATA lpWSAData ); WSAStartup 格 式: int PASCA ...
-
java中Integer包装类的具体解说(java二进制操作,全部进制转换)
程序猿都非常懒,你懂的! 今天为大家分享的是Integer这个包装类.在现实开发中,我们往往须要操作Integer,或者各种进制的转换等等.我今天就为大家具体解说一下Integer的使用吧.看代码: ...
-
如何快速轻松学习bootstrap
我以前也是通过看一些视频教程来学的,比如慕课网上的,比如51cto上的那些零基础入门bootstrap什么的,还有一些培训班里流传出来的,感觉晕乎乎的,不知所云. 还是在后面不断使用的过程中慢慢体会到 ...
-
[Swift]LeetCode931. 下降路径最小和 | Minimum Falling Path Sum
Given a square array of integers A, we want the minimum sum of a falling path through A. A falling p ...
- hadoop家族技能图谱