python多线程学习
开端
初始代码
#!/usr/bin/env python
from time import sleep, ctime
def loop0():
print('start loop 0 at:', ctime())
sleep(4)
print('loop 0 done at:', ctime())
def loop1():
print('start loop 1 at:', ctime())
sleep(2)
print('loop 1 done at:', ctime())
def main():
print('starting at:', ctime())
loop0()
loop1()
print('all DONE at:',ctime())
if __name__ == '__main__':
main()
starting at: Tue Nov 24 09:06:35 2020 #单线程执行 逻辑上需要6秒多,实际也是6秒多
start loop 0 at: Tue Nov 24 09:06:35 2020
loop 0 done at: Tue Nov 24 09:06:39 2020
start loop 1 at: Tue Nov 24 09:06:39 2020
loop 1 done at: Tue Nov 24 09:06:41 2020
all DONE at: Tue Nov 24 09:06:41 2020
分析
-
程序执行过程为 main开始 loop0开始 loop0结束 loop1开始 loop1结束 main结束
-
loop0 和 loop1 是没有关联的两个过程,先执行的那个对另一个不会有影响,但是该程序执行过程中loop1必须要等loop0执行完毕才能开始。
-
可行的情况下 最少的时间应该是4秒多(MAX(loop0,loop1))
thread模块
thread模块和锁对象
函数/方法 | 描述 |
---|---|
start_new_thread(function,args,kwargs=None) | 派生一个新的线程,使用给定的args和可选的kwargs来执行function |
allocate_lock() | 分配LockType锁对象 |
exit() | 给线程退出指令 |
LockType锁对象的方法 | |
acquire(wait=None) | 尝试获取锁对象 |
locked() | 如果获取了锁对象则返回True,否则,返回false |
release() | 释放锁 |
使用thread的代码
#!/usr/bin/env python
from time import sleep, ctime
import _thread #导入thread python3改为_thread
def loop0():
print("start loop 0 at:", ctime())
sleep(4)
print('loop 0 done at:', ctime())
def loop1():
print('start loop 1 at:', ctime())
sleep(2)
print('loo(p 1 done at:', ctime())
def main():
print('starting at:', ctime())
_thread.start_new_thread(loop0, ()) #loop0开启新线程
_thread.start_new_thread(loop1, ()) #loop1开启新线程
sleep(6) #主线程睡眠保证正常执行
print('all DONE at:', ctime())
if __name__ == '__main__':
main()
starting at: Tue Nov 24 10:34:50 2020 #多线程执行 到loop1 loop2执行完成花费了4秒
start loop 0 at: Tue Nov 24 10:34:50 2020 #MAX(loop0,loop1)
start loop 1 at: Tue Nov 24 10:34:50 2020
loop 1 done at: Tue Nov 24 10:34:52 2020
loop 0 done at: Tue Nov 24 10:34:54 2020
all DONE at: Tue Nov 24 10:34:56 2020
分析
- 开启了多线程,同时执行两个没有实际联系的函数,花费时间为MAX(loop0,loop1),相比于单线程 快了2秒
- _thread.start_new_thread() 需要执行的函数和一个必须的空元组
- main函数多了睡眠6秒的操作,目的是保证在loop0、1执行完成前不退出,但是这种用睡眠主程序来保证正常运行的方法在其他线程函数数量和时间不可测的情况下是很难使用的,所以就有了锁发挥的空间了
使用thread和锁的代码
#!/usr/bin/env python
import _thread
from time import sleep, ctime
loops = [4, 2]
def loop(nloop, nsec, lock):
print('start loop', nloop, 'at:', ctime())
sleep(nsec)
print('loop', nloop, 'done at:', ctime())
lock.release() #执行完成释放相对应的锁
def main():
print('starting at:', ctime())
locks = []
nloops = range(len(loops))
for i in nloops:
lock = _thread.allocate_lock() #使用_thread.allocate_lock函数来获得锁对象
lock.acquire() #使用锁对象lock.acquire方法取得锁(上锁)
locks.append(lock) #添加到锁列表locks中
for i in nloops:
_thread.start_new_thread(loop,
(i, loops[i], locks[i]))
for i in nloops:
while locks[i].locked(): pass #检查每个锁是否锁着
print('all DONE at:', ctime())
if __name__ == '__main__':
main()
starting at: Tue Nov 24 14:01:36 2020 #总时长为4秒多
start loop 0 at: start loop 1 at: Tue Nov 24 14:01:36 2020
Tue Nov 24 14:01:36 2020
loop 1 done at: Tue Nov 24 14:01:38 2020
loop 0 done at: Tue Nov 24 14:01:40 2020
all DONE at: Tue Nov 24 14:01:40 2020
总结
- 本质上是最后一个循环检索锁的状态来达到停住主线程的方法,在分线程中完成后解开自身的锁
- 书上推荐用threading模块来完成多线程编程
threading模块
threading模块对象
对象 | 描述 |
---|---|
Thread | 表示一个执行线程的对象 |
Lock | 锁原语对象(同thread) |
Rlock | 可重入锁对象,使单一线程可以(再次)获得已持有的锁(递归锁) |
Condition | 条件变量对象,使得一个线程等待另一个线程满足特定的“条件”,比如改变状态或某个数据组 |
Event | 条件变量的通用版本, |
Semaphore | 为线程间共享的有限资源提供了一个“计数器”,如果没有可用资源时会被阻塞 |
BoundedSemaphore | 与Semaphore相似,不过它不允许超过初始值 |
Timer | 与Thread相似,不过它要在运行前等待一段时间 |
Barrier | 创建一个“障碍”,必须达到指定数量的线程后才可以继续 |
Thread类
属性 | 描述 |
---|---|
name | 线程名 |
ident | 线程的标识符 |
daemon | 布尔标志,表示这个线程是否是守护线程 |
方法 | 描述 |
init() | 实例化一个线程对象,可调用的target,参数args或kwargs |
start() | 开始执行该线程 |
run() | 定义线程功能的方法 |
join(timeout=None) | 直至启动的线程终止之前一直挂起;除非给出了timeout(秒),否则会一直阻塞 |
is_alive() | 布尔标志,表示这个线程是否还存活 |
使用threading类代码
#!/usr/bin/env python
import threading
from time import sleep, ctime
loops = [4, 2]
def loop(nloop, nsec):
print('start loop', nloop, 'at:', ctime())
sleep(nsec)
print('loop', nloop, 'done at:', ctime())
def main():
print('starting at:', ctime())
threads = []
nloops = range(len(loops))
for i in nloops:
t = threading.Thread(target=loop,
args=(i, loops[i]))
threads.append(t) #创建线程列表
for i in nloops: #逐个开启线程
threads[i].start() #区别于_thread 需要start()来手动启动
for i in nloops:
threads[i].join() #join(timeout=x(秒))自旋锁
#相比于无限循环,多了个超时时间的设定 更加合理
#本身在线程启动时一直执行,调用是为了等待线程完成
print('all DONE at:', ctime())
if __name__ == '__main__':
main()
starting at: Tue Nov 24 18:43:46 2020
start loop 0 at: Tue Nov 24 18:43:46 2020
start loop 1 at: Tue Nov 24 18:43:46 2020
loop 1 done at: Tue Nov 24 18:43:48 2020
loop 0 done at: Tue Nov 24 18:43:50 2020
all DONE at: Tue Nov 24 18:43:50 2020
看下使用单线程和使用多线程速度差别
#############################################sonthreading.py 自定义类继承threading.Thread
class MyThread(threading.Thread):
def __init__(self, func, args, name=''):
threading.Thread.__init__(self)
self.name = name
self.func = func
self.args = args
self.res = ''
def getresult(self):
return self.res
def run(self):
print('starting ', self.name, 'at:', ctime())
self.res = self.func(*self.args)
print(self.name, 'finished at: ', ctime())
########################################单线程和多线程跑下三种求值
from sonthreading import MyThread
from time import ctime, sleep
def fib(x):
sleep(0.005)
if x < 2:
return 1
return fib(x - 2) + fib(x - 1)
def fac(x):
sleep(0.1)
if x < 2:
return 1
return x + fac(x - 1)
def add_sum(x):
sleep(0.1)
if x < 2:
return 1
return x + add_sum(x - 1)
funcs = [fib, fac, add_sum]
n = 14
def main():
nfuncs = range(len(funcs))
print('*** SINGLE THREAD')
for i in nfuncs:
print('starting', funcs[i].__name__, 'at:', ctime())
# print(i)
print(funcs[i](n))
print(funcs[i].__name__, 'finished at:', ctime())
print('\n*** MULTIPLE THREADS')
threads = []
for i in nfuncs:
t = MyThread(funcs[i], (n,),
funcs[i].__name__)
threads.append(t)
for i in nfuncs:
threads[i].start()
for i in nfuncs:
threads[i].join()
print(f"self.res: {threads[i].getresult()}")
print('all DONE')
if __name__ == '__main__':
main()
##################################输出
*** SINGLE THREAD #单线程用时10秒
starting fib at: Sat Nov 28 10:42:08 2020
610
fib finished at: Sat Nov 28 10:42:15 2020
starting fac at: Sat Nov 28 10:42:15 2020
105
fac finished at: Sat Nov 28 10:42:16 2020
starting add_sum at: Sat Nov 28 10:42:16 2020
105
add_sum finished at: Sat Nov 28 10:42:18 2020
*** MULTIPLE THREADS #多线程用时7秒
starting fib at: Sat Nov 28 10:42:18 2020
starting fac at: Sat Nov 28 10:42:18 2020
starting add_sum at: Sat Nov 28 10:42:18 2020
fac finished at: Sat Nov 28 10:42:19 2020
add_sum finished at: Sat Nov 28 10:42:19 2020
fib finished at: Sat Nov 28 10:42:25 2020
self.res: 610
self.res: 105
self.res: 105
all DONE
Process finished with exit code 0
先这样吧 先补操作系统比较好