Python并发编程基础 №⑥ 线程队列、守护线程、条件和定时器、线程池、线程的事件和信号量、线程锁

1、线程队列

在线程中也可以使用队列传递数据,原因是它自带锁,方便,保证数据安全!
 1 # 1、队列
 2 # 在线程中也可以使用队列传递数据,原因是它自带锁,方便,保证数据安全!不用每
 3 q = queue.Queue()
 4 q.put(1)
 5 q.put(2)
 6 q.put(3)
 7 print(q.get())  # 队列的特点:先进先出。
 8 
 9 
10 # 2.栈 性质的队列
11 lifo = queue.LifoQueue()
12 lifo.put(1)
13 lifo.put(2)
14 print(lifo.get())  # 栈的特点:先进后出
15 
16 
17 # 3.带优先级的队列
18 pq = queue.PriorityQueue()
19 pq.put((-1, 'a'))
20 pq.put((1, 'b'))
21 pq.put((-1, 'c'))
22 
23 
24 print(pq.get())  # 先比较数字,然后比较后面内容的ascii码,数字一样的话,码小的输出! #(0, 'a')
View Code

2、守护线程

 1 from threading import Thread
 2 import time
 3 
 4 def func():
 5     while True:
 6         time.sleep(1)
 7         print('***'*3)
 8 
 9 def call():
10     time.sleep(5)
11     print('call in ....')
12 
13 thread = Thread(target=func,)
14 thread.daemon = True  # 开启该子线程的守护线程模式:它会随着主线程的运行完成而结束
15 thread.start()
16 t = Thread(target=call,)
17 t.start()
18 print('主线程代码执行完毕!')  # 如果主线程下的其他非守护线程没结束,它也不结束
19 
20 # 对比守护进程:守护进程会随着主进程的代码执行完毕,而结束!
View Code

3、线程锁

import time, random
from threading import Lock, Thread

# 这个Lock跟进程中的锁,都是互斥锁。存在如果同时有多把锁的话,有可能产生死锁的问题!

# Lock 互斥锁
total = 10

def func(lock):
    global total
    # lock.acquire()
    tmp = total
    time.sleep(1)  # 放在此处才有效果,10减10次后不是0,数据不安全,虽然cpython计时器中有GIL锁,
    # 同一时刻只能让一个线程访问cpu,但是此时刚好cpu时间片到了,必须释放,其他线程于是可以继续访问的了@ 所以还得加锁,保护数据!
    tmp -= 1
    total = tmp
    # lock.release()

lock = Lock()

t_lst = []
for i in range(10):
    t = Thread(target=func, args=(lock, ))
    t.start()
    t_lst.append(t)

[t.join() for t in t_lst]  # 加锁会等10秒,才会有结果! 效率降低,但是数据安全

print(total)

# 典型死锁例子:科学家们吃意面
''' 有多位科学家在一起聚餐,可是桌子上只有一盘面和一个叉子;他们又很饿,于是他们开始抢了,
只有同时抢到叉子和盘子的人,才能吃上面!可是大概率的发生的却是:一位抢到叉子,另外一位抢到盘子,都吃不上面了!'''

# 同步锁或互斥锁
# fork_lock = Lock()
# disk_lock = Lock()

# 递归锁
from threading import RLock
fork_lock = disk_lock = RLock()

def eat_noodle(name):
    disk_lock.acquire()    # 与delay中的一定要相反,先拿到盘子
    print(name, '拿到了盘子')
    fork_lock.acquire()
    print(name, '拿到了叉子')
    print(' is eating delicious Italy Noodles')
    fork_lock.release()
    disk_lock.release()

def eat_noodle_delay(name):
    fork_lock.acquire()    # 与上面的函数相反,先拿叉子,这样才会有死锁发生!
    print(name, '拿到了叉子')
    time.sleep(random.randint(1, 3))  # 有延时
    disk_lock.acquire()
    print(name, '拿到了盘子')
    print(name, ' is eating delicious Italy Noodles')
    disk_lock.release()
    fork_lock.release()


Thread(target=eat_noodle, args=('tom', )).start()
Thread(target=eat_noodle_delay, args=('jack',)).start()

Thread(target=eat_noodle, args=('trump',)).start()
Thread(target=eat_noodle_delay, args=('steven',)).start()


# 解决死锁的好办法:用递归锁
from threading import RLock

rlock = RLock()
rlock.acquire()
rlock.acquire()
print('rlock is here')   # 不同于互斥锁,可以执行此句!
rlock.release()
rlock.release()

# 所以用递归锁可以解决上面的死锁问题,
# 直接将两把锁都用递归锁
# fork_lock = disk_lock = RLock()  # 一个钥匙串上的两把钥匙

 

4、条件:Condition 特殊的锁

 1 from threading import Condition, Thread
 2 
 3 # acquire release
 4 # 一个条件被创建之初 默认有一个False状态
 5 # False状态 会影响wait一直处于等待状态
 6 # notify(int数据类型)  造钥匙
 7 
 8 
 9 def func(index, condition):
10     condition.acquire()
11     condition.wait()  # 等钥匙
12     print('在第%s次循环里' % index)
13     condition.release()
14 
15 
16 condition = Condition()  # 默认有一个递归锁
17 
18 for i in range(12):
19     Thread(target=func, args=(i, condition)).start()
20 
21 while 1:
22     try:
23         num = int(input('>>>').strip())
24         condition.acquire()
25         condition.notify(num)  # 制造3把一次性的锁
26         condition.release()
27     except ValueError:
28         print('请输入整数!')
29         continue
View Code

5、定时器 延迟启动的线程

 1 import time
 2 from threading import Timer
 3 
 4 # Timer是继承Thread类的,因此它也可以实现多线程,是延时启动的线程
 5 
 6 
 7 def sync_time():
 8     print('与格林威治时间同步!')
 9 
10 
11 while True:
12     t = Timer(2, sync_time)
13     t.start()
14     time.sleep(2)  # 此处不等待2秒,子线程会在等待2秒后不断输出!因为上述代码是不阻塞的,异步进行的!
View Code

6、线程池

 1 from concurrent.futures import ThreadPoolExecutor
 2 import time
 3 from concurrent.futures import ProcessPoolExecutor
 4 
 5 
 6 def run():
 7     time.sleep(2)
 8     print(' i am running ')
 9 
10 
11 def func(num):
12     time.sleep(1)
13     ret = num + 1
14     # print(ret)
15     return ret
16 
17 
18 def deal(ret):
19     print('-------', ret.result())
20 
21 
22 pool = ThreadPoolExecutor(max_workers=5)  #  默认 不要超过cpu个数*5
23 
24 
25 pool.map(run, range(20))  # 没有返回值
26 
27 t_lst = []
28 for i in range(20):
29     t = pool.submit(func, i)
30     t_lst.append(t)
31 
32 # pool.shutdown()
33 
34 for t in t_lst:
35     print(t.result())   # 一次性拿返回值浪费时间,可以随时取,即不关闭池即可实现!
36 
37 # 回调函数
38 for i in range(20):
39     t = pool.submit(func, i).add_done_callback(deal)
40 
41 
42 # 2.线程池ProcessPoolExecutor的用法同上
43 p_pool = ProcessPoolExecutor(max_workers=7)  # 默认比核数多1
44 '''形同代码就省略了'''
View Code

7、线程的事件 Event

 1 import time
 2 import random
 3 from threading import Thread
 4 from threading import Event
 5 
 6 # 复习在进程中事件:
 7 # 创建的是带状态的:False
 8 # 如果此时使用wait方法,就会阻塞!
 9 # 判断状态的方法:is_set()
10 # 转换状态的方法:set() 设为True clear()设为False
11 
12 # 进程中举的例子是交通灯
13 # 线程中不重复了,就举连接数据的例子,一个线程连接,一个线程检查是否连接成功
14 
15 
16 def check_conn(event):
17     time.sleep(random.randint(4, 6))
18     event.set()
19 
20 
21 def conn_db(event):
22     count = 0
23     while count < 5:
24         time.sleep(0.1)
25         e.wait(1)
26         if event.is_set():
27             print('连接数据库成功!')
28             break
29         else:
30             count += 1
31             print('第%d次,连接数据库不成功!' % count)
32     else:
33         raise TimeoutError('连接数据库超时!')
34 
35 
36 e = Event()
37 
38 t1 = Thread(target=check_conn, args=(e, ))
39 t2 = Thread(target=conn_db, args=(e, ))
40 
41 t1.start()
42 t2.start()
View Code

8、线程的信号量 Semaphore

 1 import time
 2 import random
 3 from threading import Semaphore, Thread
 4 # 回顾进程中的Semaphore,会联想到进ktv的例子,一次只能同时让N个人进去!
 5 
 6 
 7 def enter_ktv(semaphore, name):
 8     time.sleep(random.randint(1, 3))
 9     semaphore.acquire()
10     print(name, 'is singing!')
11     semaphore.release()
12 
13 
14 sp = Semaphore(5)   # 一次可以允许5个线程访问函数中的print
15 
16 for i in range(20):
17     t = Thread(target=enter_ktv, args=(sp, i))
18     t.start()
View Code
posted @ 2020-01-01 21:48  四方游览  阅读(170)  评论(0)    收藏  举报