一、 用法: 输出: activeCount()输出为: 2.current_thread() , currentThread() 输出: 3. get_ident() 4.enumerate() 4.1 输出: 列表后3个分别对应a_1,a_2,a_3 这三个子线程 输出: 4.3 输出: 5.stack_size() 输出 6.main_threade() 输出: 二、 Thread是threading中比较重要的一个类。 输出: 2.threading.Thread的run() 下面创建线程类。 输出: 如果输出乱的话可以再命令提示符里面运行。 2.threading.Thread的join() 输出: 3.threading.Thread的is_alive() , is_Alive() 输出: 为什么输出了两个False呢? 输出: 分析: 如果加一行代码: 输出: 分析: 4.threading.Thread的daemon 需要在命令提示符里面运行daemon才起作用。 分析: 三、Lock与RLock对象 输出: 分析: 2.RLock 加一把锁 输出: 加两把锁 输出: 在使用锁的同时,要避免死锁, 四、Condition 分析: 修改一下: 五、Queue对象 输出: 推荐个队列讲解的链接点击进入 六、Event对象 1. 输出: 分析: 2. 输出: 分析: 输出: 七、Semaphore与BoundedSemaphore 1.Semaphore 输出: 由上面的时间戳可以看出基本上一次性启动5个线程 2.BoundedSemaphore 输出: 每一秒启动两个线程。 这样做会报错。 没有报错。 八、Barrier 输出:
1.active_count() , activeConut()
方法
功能
active_count() , activeConut()
返回处于alive状态的Thread对象数量
import time import threading def sing(): print("---singing---") time.sleep(10) print(threading.active_count())
2
当前处于alive的Thread对象为:[<_MainThread(MainThread, started 9104)>, <Thread(SockThread, started daemon 7968)>]
下面给他添加线程对象:import time import threading def sing(): print("---singing---") time.sleep(10)#这里需要让线程休息10秒,不然一瞬间就完成了,然后线程结束 t_1 = threading.Thread(target = sing) #Thread是threading中比较重要的一个类。下面有介绍。 t_1.start() t_2 = threading.Thread(target = sing) t_2.start() t_3 = threading.Thread(target = sing) t_3.start() print(threading.active_count()) #这里也可以写成 #print(threading.activeCount())
5
这5个对象为:[<_MainThread(MainThread, started 16276)>, <Thread(SockThread, started daemon 9568)>, <Thread(Thread-1, started 528)>, <Thread(Thread-2, started 9384)>, <Thread(Thread-3, started 9404)>]
current_thread() , currentThread()
返回当前的Thread对象
import time import threading def sing(): print("---singing---") time.sleep(10) t_1 = threading.Thread(target = sing) t_1.start() print(threading.current_thread()) print(threading.currentThread())
---singing--- <_MainThread(MainThread, started 6880)> <_MainThread(MainThread, started 6880)>
get_ident()
返回当前线程的线程标识符。线程标识符是一个非负整数,并无特殊函数,只是用来标识线程,该整数可能会被循环利用。python3.3及以后版本支持该方法
import time import threading def sing(): print('线程t_1',threading.get_ident()) time.sleep(10) print('主线程',threading.get_ident())
import time import threading def sing(): print('线程t_1',threading.get_ident()) time.sleep(10) t_1 = threading.Thread(target=sing) t_1.start()
enumerate()
返回当前处于alive状态的所有Thread对象列表
import time import threading def sing(): time.sleep(10) t_1 = threading.Thread(target = sing) t_1.start() t_2 = threading.Thread(target = sing) t_2.start() t_3 = threading.Thread(target = sing) t_3.start() print(threading.enumerate())
[<_MainThread(MainThread, started 19200)>, <Thread(SockThread, started daemon 2064)>, <Thread(Thread-1, started 11284)>, <Thread(Thread-2, started 13036)>, <Thread(Thread-3, started 632)>]
前两个对应主线程。
4.2import time import threading def sing(): time.sleep(10) t_1 = threading.Thread(target = sing) t_1.start() t_2 = threading.Thread(target = sing) t_2.start() print(threading.enumerate())
[<_MainThread(MainThread, started 2372)>, <Thread(SockThread, started daemon 8460)>, <Thread(Thread-1, started 17548)>, <Thread(Thread-2, started 5572)>]
import time import threading def sing(): time.sleep(10) print(threading.enumerate())
[<_MainThread(MainThread, started 20176)>, <Thread(SockThread, started daemon 2536)>]
stack_size([size])
返回创建线程时使用的栈的大小,如果指定size参数,则用来指定后续创建的线程使用的栈大小,size必须是0(表示使用系统默认值)或大于32K的正整数。
import threading print(threading.stack_size())#输出当前线程栈的大小 threading.stack_size(43*1024)#设置下一个线程栈的大小43*1024=44032 print(threading.stack_size())
0 44032
main_thread()
返回主线程对象,即启动python解释器的线程对象。python3.4及以后版本支持该方法
import time import threading def sing(): time.sleep(10) print(threading.enumerate()) print(threading.main_thread())
[<_MainThread(MainThread, started 15304)>, <Thread(SockThread, started daemon 3752)>] <_MainThread(MainThread, started 15304)>
Thread 对象
threading.Thread
线程类,用于创建和管理线程
可以通过Thread类的构造函数传递一个可调用对象来创建线程
可以继承threading.Thread类创建派生类,并重写__init__和run方法,实现自定义线程对象类。
1.threading.Thread的start()
start()
自动调用 run 方法,启动线程,执行线程代码
import time import threading def sing(i): print('线程',i) time.sleep(10) t_1 = threading.Thread(target=sing,name = 't_1',args='1') t_1.start()#启动t_1线程 #输出为线程1 #还可以这样写 threading.Thread(target=sing,name = 't_2',args=(2,)).start() #输出为线程2
线程1
线程2
run()
线程代码,用来实现线程的功能与业务逻辑,可以在子类中重写该方法来自定义线程的行为
import time import threading class mythread(threading.Thread):#threading.Thread(基类) def __init__(self,numble): threading.Thread.__init__(self) #这里有个规律(我也不太懂,就是总结一下)为什么用threading.Thread.__init__(self)而不用super.__init__.(self)呢,因为class myThread(threading.Thread): #有个括号,括号里面是threading.Thread,可以多找几个例子,不带括号的用super().__init__.() #super(myThread,self).__init__()这样写也对 self.numble = numble def run(self): print(self.numble) if __name__ == '__main__': t_1 = mythread('线程t_1')#创建线程 t_1.start()#启动线程
线程t_1
还可以这样做:import time import threading def mythread(numble): print(t_1.getName())#也可以在这里输出,因为t_1这个对象已经被传入。不懂再复习一下前面的学的 print(numble) if __name__ == '__main__': t_1 = threading.Thread(target=mythread,name='t_1线程',args=(1,))#target对应函数,name对应线程名字,args对应传入的数据 t_1.start() print(t_1.getName())
join()
join([timeour]) :阻塞当前线程,等待被调线程结束或超时后再继续执行当前线程的后续代码,参数 timeout 用来指定最长等待时间,单位为秒。
import time import threading def mythread(numble): for i in range(numble): print(i) time.sleep(10) if __name__ == '__main__': t_1 = threading.Thread(target=mythread,args=(4,)) t_1.start() t_1.join(3)#只等3秒mythread函数有一行代码time.sleep(10),但是我们加了一个t_1.join(3) #不管他休息几秒,join里面是3秒,3秒内没运行完也不会等了, #可以数一下时间看看是不是3秒后执行t_2线程。 t_2 = threading.Thread(target=mythread,args=(5,)) t_2.start() t_2.join()
0 1 2 3 0 1 2 3 4
is_alive() , is_Alive()
测试线程是否处于 alive 状态
import time import threading def mythread(numble): for i in range(numble): pass #print(i) time.sleep(10) if __name__ == '__main__': t_1 = threading.Thread(target=mythread,args=(4,)) t_1.start() t_1.join(3) t_2 = threading.Thread(target=mythread,args=(5,)) t_2.start() t_2.join() print(t_1.isAlive()) print(t_2.isAlive())
False False
分析一下:t_1.join(3),t_1运行3秒后开始运行t_2,但是此时t_1还是alive状态, t_1在还剩7秒的时候,开始运行t_2,t_2可以运行10秒,在这10秒运行的同时 t_1的7秒已经运行完了。
import time import threading def mythread(numble): for i in range(numble): pass #print(i) time.sleep(10) if __name__ == '__main__': t_1 = threading.Thread(target=mythread,args=(4,)) t_1.start() t_1.join(3) t_2 = threading.Thread(target=mythread,args=(5,)) t_2.start() t_2.join(1) print(t_1.isAlive()) print(t_2.isAlive())
True True
t_1.join(3),t_1运行3秒后开始运行t_2,t_1还剩7秒 没有运行完,然后t_2开始,因为加了个t_2.join(1), t_2运行了一秒,此时t_2还剩9秒,t_1还剩6秒, 所以t_1与t_2都是alive状态。
import time import threading def mythread(numble): for i in range(numble): pass #print(i) time.sleep(10) if __name__ == '__main__': t_1 = threading.Thread(target=mythread,args=(4,)) t_1.start() t_1.join(3) t_2 = threading.Thread(target=mythread,args=(5,)) t_2.start() t_2.join(1) print(t_1.isAlive()) time.sleep(10) print(t_2.isAlive())
True False
因为time.sleep(10),主进程阻塞了10秒,在阻塞之前t_1已经输出, 但是t_2还没有,t_2带着它仅剩的9秒时间等了10秒,早就结束了, 所以t_2不是alive状态。
daemon
在脚本运行过程中有一个主线程,若在主线程中创建了子线程,当主线程结束时根据子线程 daemon 属性值的不同可能会发生下面的两种情况之一:当某子线程的 daemon 属性为 Fasle 时,主线程结束时会检测该子线程是否结束,如果该子线程尚未完成,则主线程会等待它完成后再退出。当某子线程的 daemon 属性为 True时,主线程运行结束时不对该子线程进行检查而直接退出,同时所有 daemon 值为 True 的子线程将随主线程一起结束,而不论是否运行完成。daemon 属性的值默认为 False ,如果需要修改,则必须在调用 start() 方法启动线程之前修改。以上论述不适用于 IDLE 环境中的交互模式或脚本运行模式,因为在该环境中的主线程只有在退出 python IDLE 时才终止。
import time import threading class mythread(threading.Thread):#threading.Thread(基类) def __init__(self,numble): threading.Thread.__init__(self) #这里有个规律(我也不太懂,就是总结一下)为什么用threading.Thread.__init__(self)而不用super.__init__.(self)呢,因为class myThread(threading.Thread): #有个括号,括号里面是threading.Thread,可以多找几个例子,不带括号的用super().__init__.() #super(myThread,self).__init__()这样写也对 self.numble = numble def run(self): time.sleep(self.numble) print(self.numble) if __name__ == '__main__': t_1 = mythread(3)#创建线程t_1 t_2 = mythread(4)#创建线程t_2 print(t_1.daemon) print(t_2.daemon) t_2.daemon = True print(t_1.daemon) print(t_2.daemon) t_1.start()#启动线程t_1 t_2.start()#启动线程t_2
输出:False False False True 3
主函数在运行到t_2.start()时,就结束了。因为t_1的daemon为False,所以主函数 等待t_1运行结束,输出了3; 因为t_2的daemon为True,所以主函数不会等它,于是t_2就没有输出了。
1.Lock
Lock
Lock是 比较低级的同步原语,当被锁定以后不属于特定的线程。一个锁有两种状态: locked和unlocked。 如果锁处于unclocked状态,acquire(方 法将其修改为locked并立即返回;如果锁已处于locked状态,则阻塞当前线程并等待其他线程释放锁然后将其修改为locked并立即返回,或等待一定的时间后返回但不修改锁的状态。release()方法将锁状态由locked修改为unlocked并立即返回,如果锁状态本来已经是unlocked,调用该方法将会抛出异常。
import time import threading class mythread(threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): global x lock.acquire() for i in range(3): x = x + i time.sleep(2) print(x) lock.release() lock = threading.Lock()#创建锁#也可以写为lock = threading.RLock() t_1 = [] for i in range(10): t = mythread() t_1.append(t) x = 0 for i in t_1: i.start()
3 6 9 12 15 18 21 24 27 30
明明创建了10个进程,如果不加锁,这10个进程谁先进入cpu,谁就先运行, 输出就是杂乱无章的 但事实上我们加了锁,当第一个进程启动并运行到lock.acquire()时,他就获得了锁, 那么其他进程运行到lock.acquire()时,如果第一个获得锁的进程还没有执行 lock.release()把锁释放,那么,这些进程就会堵在这,直到第一个获得锁的进程 执行了lock.release(),其他进程就开始抢夺这一把锁,谁抢到,谁执行lock.acquire() 下面的代码。
RLock
RLock与Lock的主要区别:在同一线程内,对RLock进行多次acquire()操作,程序不会阻塞。也就是说,在一个线程内,可以执行多个lock.acquire(),同样当我们想要解除阻塞的时候需要执行同样个数的lock.release()才可以。
import time import threading class mythread(threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): global x lock.acquire() for i in range(3): x = x + i time.sleep(2) print(x) lock.release() lock = threading.RLock() t_1 = [] for i in range(10): t = mythread() t_1.append(t) x = 0 for i in t_1: i.start() i.join()
3 6 9 12 15 18 21 24 27 30
import time import threading class mythread(threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): global x lock.acquire() lock.acquire() for i in range(3): x = x + i time.sleep(2) print(x) lock.release() lock.release() lock = threading.RLock() t_1 = [] for i in range(10): t = mythread() t_1.append(t) x = 0 for i in t_1: i.start() i.join()
3 6 9 12 15 18 21 24 27 30
简单叙述一下:就是在运行多个线程的时候,每个线程都需要获取两把锁才可以运行,但是因为 代码逻辑问题,导致甲线程获得了锁一,却没有获得锁二, 而乙进程获得了锁二,却没有获得锁一,导致两个线程都无法运行, 这就是死锁。
Condition
使用Condition对象可以在某些事件触发后才处理数据或执行特定的功能代码,可以用于不同线程之间的通信或通知,以实现更高级别的同步。在内部实现上,Condition对象总是与某种锁对象相关联。Condition对象支持 上下文管理语句with。Condition对 象除了具有acquire()和release()方法之外,还有wait()、wait_ for()、 notify()、 notify all()等 方法。
wait()
wait(timeout=None)方法 会释放锁,并阻塞当前线程直到超时或其他线程针对同一个Condition对象调用了notify(),notify_ all()方法, 被唤醒之后当前线程会重新尝试获取锁并在成功获取锁之后结束wait()方法,然后继续执行。
wait_for()
wait_for(predicate, timeout=None)方法阻塞当前线程直到超时或者指定条件得到满足
notify
notify(n=1)唤醒等待该Condition对象的一个或多个线程,该方法并不负责释放锁.
notify_all()
notify_all()方 法会唤醒等待该Condition对象的所有线程。
import threading import time condition = threading.Condition() def a_1(): print('已经进入a_1') condition.acquire() print('a_1获得了') print('我这就去告诉a_2,并把condiion交给a_2') condition.wait()#把锁释放掉,阻塞当前线程。 print('a_2终于把condition还给我了。') condition.release() print('a_1release') def a_2(): time.sleep(1) print('好的我知道了,谢谢你的帮助。') print('已经进入a_2') condition.acquire() print('a_2获得了') print('我也获得了,condition就还给你吧。') condition.notify(1)#唤醒此时一个正在等待的线程 condition.wait() print('a_2终于出来了') condition.release() print('a_2release') def a_3(): b_1 = threading.Thread(target=a_1) b_2 = threading.Thread(target=a_2) b_1.start() b_2.start() b_1.join() b_2.join() if __name__ == '__main__': a_3()
已经进入a_1 a_1获得了 我这就去告诉a_2,并把condiion交给a_2 好的我知道了,谢谢你的帮助。 已经进入a_2 a_2获得了 我也获得了,condition就还给你吧。 a_2终于把condition还给我了。 a_1release
刚开始b_1,b_2进入了各自的函数,开始运行,但是b_2睡了1秒, 导致b_2运行进度比b_1慢,在b_2睡觉的时候b_1已经运行到了condition.wait(), b_1就把锁释放掉,阻塞当前线程。以上就是b_1趁着b_2睡觉时间干的事。 b_2睡醒后开始运行condition.notify(1),唤醒此时一个正在等待的线程, 此时正在等待的只有b_1,所以b_1被唤醒,(如果condition.notify(0)这样写, 将唤醒0个线程),b_1被唤醒后,开始运行a_1中condition.wait()下面的代码, 并偷偷地释放了condition,运行结束。可怜的b_2还在傻傻等着b_1唤醒自己。
import threading import time condition = threading.Condition() def a_1(): print('已经进入a_1') condition.acquire() print('a_1获得了') print('我这就去告诉a_2,并把condiion交给a_2') condition.wait()#把锁释放掉,阻塞当前线程。 print('a_2终于把condition还给我了。') condition.notify() condition.release() print('a_1release') def a_2(): time.sleep(1) print('好的我知道了,谢谢你的帮助。') print('已经进入a_2') condition.acquire() print('a_2获得了') print('我也获得了,condition就还给你吧。') condition.notify(1)#唤醒此时一个正在等待的线程 condition.wait() print('我就知道你不会把我忘了的。') condition.release() print('a_2release') def a_3(): b_1 = threading.Thread(target=a_1) b_2 = threading.Thread(target=a_2) b_1.start() b_2.start() b_1.join() b_2.join() if __name__ == '__main__': a_3() print('结束')
已经进入a_1 a_1获得了 我这就去告诉a_2,并把condiion交给a_2 好的我知道了,谢谢你的帮助。 已经进入a_2 a_2获得了 我也获得了,condition就还给你吧。 a_2终于把condition还给我了。 a_1release 我就知道你不会把我忘了的。 a_2release 结束
Queue
Queue对象主要实现了put()和get()方法,分别用来往队列尾部追加元素和在队列头部获取并删除元素。put(item, block=True, timeout=None) get(block=True, timeout=None)
import threading import time import queue myqueue = queue.Queue() def a_1(): global myqueue for i in range(4): myqueue.get() print('队列中剩余元素个数',myqueue.qsize()) def a_2(): global myqueue for i in range(4): myqueue.put(i)#放入 print(i) print('队列中剩余元素个数',myqueue.qsize()) b_2 = threading.Thread(target=a_2) b_1 = threading.Thread(target=a_1) b_2.start() b_2.join() b_1.start() b_1.join()
0 1 2 3 队列中剩余元素个数 4 队列中剩余元素个数 0
Event
Event对象的set()方法可以设置Event对象内部的信号标志为真;Event对象的clear()方法可以清除Event对象内部的信号标志,将其设置为假; .Event对象的isSet()方法用来判断其内部信号标志的状态;Event对象的wait()方法只有在其内部信号状态为真时将很快地执行并返回;若Event对象的内部信号标志为假,wait方法将一直等待至超时或内部信号状态为真。
import threading myevent = threading.Event() print(myevent.isSet())#输出为False,可知默认为False。 myevent.set() print(myevent.isSet()) myevent.clear() print(myevent.isSet())
False True False
第一个False为默认情况。 set之后,在输出iSset,为True,表示Event对象内部的信号标志为真 clear之后,为False,清除Event对象内部的信号标志,此时信号标准为假
import threading myevent = threading.Event() myevent.set() myevent.set() myevent.set() myevent.set() myevent.clear() print(myevent.isSet())
False
不管set了几次,clear一次,全部玩完。
import threading import time myevent = threading.Event() def a_1(numble): if myevent.isSet(): print('大门已经打开,请迅速进入。') else: print('大门已经关闭了。请等待!') print('学生{}正在等待'.format(numble)) time.sleep(1) myevent.wait() print('学生{}已经进入了教室'.format(numble)) def a_2(): print(b_3.getName(),'来了,他掏出了钥匙,打开了门。') print('门打开了。') print('班长等待学生全部进入教室。') myevent.set() time.sleep(3) print('班长进入了教室。') if __name__ == '__main__': myevent.clear() b_1 = threading.Thread(target=a_1,args=(1,)) b_2 = threading.Thread(target=a_1,args=(2,)) b_3 = threading.Thread(target=a_2,name='班长') b_1.start() b_2.start() time.sleep(3) b_3.start() time.sleep(6) #这里睡了6秒,就是为了让班长进入教室的时间比老师早,不然班长还没进教室,老师就已经进了。 print('老师来了,开始上课。')
大门已经关闭了。请等待! 大门已经关闭了。请等待! 学生1正在等待 学生2正在等待 班长 来了,他掏出了钥匙,打开了门。 门打开了。 班长等待学生全部进入教室。 学生1已经进入了教室 学生2已经进入了教室 班长进入了教室。 老师来了,开始上课。
Semaphore与BoundedSemaphore
Semaphore对象维护着-一个内部计数器,调用acquire()方法时该计数器减1,调用release()方法时该计数器加1,适用于需要控制特定资源的并发访问线程数量的场合。调用acquire()方法时, 如果计数器已经为0则阻塞当前线程,直到有其他线程调用了release()方法,所以计数器的值永远不会小于0。Semaphore对象可以调用任意次release()方法,而BoundedSemaphore对象可以保证计数器的值不超过特定的值。
Semaphore
threading.Semaphore([value]) values是一个内部计数,values默认是1,如果小于0,则会抛出 ValueError 异常,可以用于控制线程数并发数
import threading import time S = threading.Semaphore(5) #可以同时启动5个线程 def a_1(numble): S.acquire()#阻塞式请求获得线程(信号量减1) time.sleep(1) print(numble,time.time())#输出牌号与时间戳 S.release()#释放资源(信号量加1) def b_1(): threads = list() for i in range(10):#准备启动10个线程 threads.append(threading.Thread(target=a_1, args=(i,))) threads[-1].start() for t in threads: t.join() print('所有线程工作结束') if __name__ == '__main__': b_1()
0 1588401833.5383408 3 1588401833.539341 2 1588401833.539341 4 1588401833.539341 1 1588401833.539341 5 1588401834.5388339 9 1588401834.5398383 8 1588401834.5398383 7 1588401834.5398383 6 1588401834.5398383 所有线程工作结束
import threading import time def a_1(numble): Boun.acquire() time.sleep(1) print('线程',numble,time.time()) Boun.release() Boun = threading.BoundedSemaphore(2) for i in range(10): b_1 = threading.Thread(target=a_1,args=(i,)) b_1.start()
线程 0 1588402326.64822 线程 1 1588402326.64822 线程 2 1588402327.6486 线程 3 1588402327.6495996 线程 4 1588402328.648927 线程 5 1588402328.6499276 线程 6 1588402329.649535 线程 7 1588402329.6505356 线程 8 1588402330.649914 线程 9 1588402330.6509147
3.Semaphore与BoundSemaphore的区别import threading Boun = threading.BoundedSemaphore(2) #Boun.acquire() Boun.release()
再看看Semaphoreimport threading Boun = threading.Semaphore(2) #Boun.acquire() Boun.release() Boun.release() Boun.release() Boun.release() Boun.release() Boun.release()
可以发现,普通信号量可以被无数次的释放。 有限制的信号量,在没有被acquire前不可以被释放。
Barrier
Barrier对象常用来实现这样的线程同步,多个线程运行到某个时间点以后每个线程都需要等着其他线程准备好以后,再同时进行下-步工作。
wait()
Barrier对 象最常用的方法是wait()。线程调用该方法后会阻塞,当所有线程都调用了该方法后,会被同时释放并继续执行后面的代码。Barrier对 象的wait()方法会返回-一个 介于0到parties-1之间的整数,每个线程都会得到一个不同的整数。
import threading import time import random def a_1(numble): times=random.randint(1,9) time.sleep(times) num=Bar.wait(10)#因为下面threading.Barrier(parties=3,action=a_2,timeout=10)中timeout=10,所以 #如果wait()里面不写数的话,就默认等待10秒。 print('wait返回值{} 线程参数{} 随机等待时间{}'.format(num,numble,times)) def a_2(): print('已经进入等待') Bar = threading.Barrier(parties=4,action=a_2,timeout=10) #parties最多启动的线程,action wait()全部线程都进入等待后运行的函数,timeout 默认等待的时间(可修改) b_1 = threading.Thread(target=a_1,args=(1,)) b_2 = threading.Thread(target=a_1,args=(2,)) b_3 = threading.Thread(target=a_1,args=(3,)) b_4 = threading.Thread(target=a_1,args=(4,)) b_1.start() b_2.start() b_3.start() b_4.start()
已经进入等待 wait返回值3 线程参数1 随机等待时间7 wait返回值0 线程参数2 随机等待时间2 wait返回值1 线程参数4 随机等待时间3 wait返回值2 线程参数3 随机等待时间3
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算