线程
1.条件
条件相当于一个复杂的锁。它有四个方法 acquire() release() wait() notify() notify() 这个方法第一次见,它的作用可以理解为造多个锁. Con = Condition 实例一个锁
我们举个例子来说明情况:
from threading import Thread,Condition
def func(con,i):
con.acquire()
con.wait() # 等钥匙
print('在第%s个循环里'%i)
con.release() #这里的归还相当于销毁
con = Condition()
for i in range(10):
Thread(target=func,args = (con,i)).start()
while True:
num = int(input('>>>'))
con.acquire()
con.notify(num) # 造钥匙
con.release()
运行结果:
解释:
输入3把锁,执行3次func。然后又输入2把,执行2次func 直至把起的10个进程都运行完。
需要注意的是,在代码里写到了con.acquire() con.release()这里明明归还了钥匙为什么下2个线程不能执行。我们可以把造的钥匙理解成一次性的,用了之后就销毁了。
2.定时器
Timer 就是一种可以把线程定时执行的方法
我们举个简单的例子来说明
# 定时器的讲解
def check():
print('时间校准')
while True:
t = Timer(5, check).start()
time.sleep(5)
解释:
想要做的功能就是 每5秒就去启动一个线程执行时间同步操作
如果少调time等待那五秒造成的结果就是,在很短的时间里(假如0.01秒)会启动100多个线程 他们同时等5秒后,就一起去执行时间校准。这样和我们当初设想的效果是不一样的。
3.队列
咱们之前讲到 进程之间的内存是封闭的,而线程之间的数据是共享的
那为什么我们要在线程之间用队列呢,可以用列表么?
答案:可以用列表,但是不安全。
就涉及上修改值的问题,假如a从列表里取出4然后想把它变成五,变成5后存进去,这时候时间片用完了。然后c也取了那个4进行变3操作,取出后变3,很不巧这时候时间片也用完了,轮到了a,a把5放进去,然后c把3放进去。最后的结果是a的数据错误。这样就造成了数据不安全的隐患。而使用queue 其内置的锁就可以帮我们保证数据的安全。
我们这里就举一个特别简单的例子,来介绍如何使用线程里的队列(其实和进程里的差不多)
import queue
q = queue.Queue() # 队列 先进先出
q.put(2) # q.put_nowait() q.get_nowait()
print(q.get())
线程的队列相比于进程的队列有些不一样。首先引用模块不一样,其次线程队列还有其他的形式。如栈队还有优先级队列,下面分别展示一下。
q = queue.LifoQueue() # 栈 先进后出
q.put(1)
q.put(2)
q.put(3)
print(q.get())
得到的结果是:3 按照标准队列的话,应该是先进先出。但是栈队的话,就是先进后出。
优先级队列:
q = queue.PriorityQueue() # 优先级队列
q.put((20,'a'))
q.put((10,'b'))
print(q.get()[1])
得到的结果是b 因为前面的数字是优先级,越小优先级越高。同时在村的时候以元组存进去的,所以取出的时候加个[1],就能得到我们想要的元素。
4.线程池
终于写到线程池了,走到这里不容易啊。线程池的内容和进程池差不多,只是他们引用的不是同一个模块。
线程池引用的这个:from concurrent.futures import ThreadPoolExecutor
# # 线程池
def func(i):
time.sleep(2)
print('这是线程%s'%i)
return i
# ProcessPoolExecutor 这就是进程池了 其他的不用变
tpool = ThreadPoolExecutor(max_workers=5) ThreadPoolExecutor 这是一个线程池
t_list = []
for i in range(10):
t = tpool.submit(func,i) # 这是一个线程
t_list.append(t) # 不知道大家发现了么 在多进程和多线程的时候 就不用start()
tpool.shutdown() # 并且也不用写target=func了,这里代替 了以前的
t.close() t.join()
print('这是主线程')
for t in t_list:print('******',t.result())
# 这里获得的结果就是 return的结果
# 当不用shutdown时,它得到的结果值是每个线程池数量的 也就是算出几个值 就立刻返回几个值
运行结果:
解释:
停2秒,然后输出5个线程,然后又输出另外5个线程 最后那个
t.result()结果,它是实时的。不是输出完前面10进程后,再执行下面的代码,而是算出结果了,就返回给result。之所以最后显示,是因为增加了join()
在这里补充一个map方法
def func(i):
time.sleep(2)
print('这是线程%s'%i)
return i*i
tpool = ThreadPoolExecutor(max_workers=5) # ThreadPoolExecutor 这是一个线程池
tpool.map(func,range(1,11)) # 这里代替了原来的 for循环 可以直接进行多个线程的开启
5.线程池的回调
例子:
# # 线程池的回调函数
def func(i):
time.sleep(2) # 这里的睡两秒是为了更好理解线程池的作用
print('这是线程%s'%i)
return i*i
def fun2(m):
print('回调函数得到的值%s'%m.result())
tpool = ThreadPoolExecutor(max_workers=5)
for i in range(10):
tpool.submit(func, i).add_done_callback(fun2)
这里重点题一下有几个常用的方法:
m.result() 回调函数得到的是对象,想要得到结果要 用result()方法
submit(func, i).add_done_callback(fun2) 像之前进程池一样不用写target args
而且调用回调函数只需要在后面.方法 这天func1 和func2都被执行了
done() 判断某一个线程是否完成
cancle() 取消某个任务
关于线程的学习就到这里了,之所以写下来。一是因为怕自己忘了,二是因为写博客能够锻炼自己的总结能力。希望自己能继续下去,不要三分钟热度,加油。