一. 锁:Lock (1次放1个)
线程安全,多线程操作时,内部会让所有线程排队处理。如:list/dict/Queue
线程不安全 + 人 => 排队处理。
需求:
a. 创建100个线程,在列表中追加8
b. 创建100个线程
v = []
锁
- 把自己的添加到列表中。
- 在读取列表的最后一个。
解锁
锁一个代码块:
import threading
import time
v = []
lock = threading.Lock()
def func(arg):
lock.acquire()
v.append(arg)
time.sleep(0.01)
m = v[-1]
print(arg,m)
lock.release()
for i in range(10):
t =threading.Thread(target=func,args=(i,))
t.start()
锁:RLock (1次放1个)
如果是threading.Lock, 出现两次lock.acquare()就会直接锁死了
import threading
import time
v = []
lock = threading.RLock()
def func(arg):
lock.acquire()
lock.acquire()
v.append(arg)
time.sleep(0.01)
m = v[-1]
print(arg,m)
lock.release()
lock.release()
for i in range(10):
t =threading.Thread(target=func,args=(i,))
t.start()
锁:BoundedSemaphore(1次放N个)信号量, 同一时间只能通过N个线程
import time
import threading
lock = threading.BoundedSemaphore(3)
def func(arg):
lock.acquire()
print(arg)
time.sleep(1)
lock.release()
for i in range(20):
t =threading.Thread(target=func,args=(i,))
t.start()
锁:Condition(1次放x个)
import time
import threading
lock = threading.Condition()
# ############## 方式一 ##############
def func(arg):
print('线程进来了')
lock.acquire()
lock.wait() # 加锁
print(arg)
time.sleep(1)
lock.release()
for i in range(10):
t =threading.Thread(target=func,args=(i,))
t.start()
while True:
inp = int(input('>>>'))
lock.acquire()
lock.notify(inp)
lock.release()
# ############## 方式二 ##############
def xxxx():
print('来执行函数了')
input(">>>")
# ct = threading.current_thread() # 获取当前线程
# ct.getName()
return True
def func(arg):
print('线程进来了')
lock.wait_for(xxxx)
print(arg)
time.sleep(1)
for i in range(10):
t =threading.Thread(target=func,args=(i,))
t.start()
锁:Event(1次放所有)
import time
import threading
lock = threading.Event()
def func(arg):
print('线程来了')
lock.wait() # 加锁:红灯
print(arg)
for i in range(10):
t =threading.Thread(target=func,args=(i,))
t.start()
input(">>>>")
lock.set() # 绿灯
lock.clear() # 再次变红灯
for i in range(10):
t =threading.Thread(target=func,args=(i,))
t.start()
input(">>>>")
lock.set()
总结:
线程安全: 线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
通常加锁也有2种不同的粒度的锁:
1. fine-grained(细粒度),程序员需要自行加/解锁来保证线程安全
2. coarse-grained(粗粒度),语言层面本身维护着一个全局的锁机制用来保证线程安全
前一种方式比较典型的是 Java, Jython 等, 后一种方式比较典型的是 CPython (即Python)。
线程安全,列表和字典线程安全;
加锁原因:
- 非线程安全
- 控制一段代码
二. threading.local
作用:
内部自动为每个线程维护一个空间(字典),用于当前存取属于自己的值。保证线程之间的数据隔离。
{
线程ID: {...}
线程ID: {...}
线程ID: {...}
线程ID: {...}
}
示例:
import time
import threading
v = threading.local()
def func(arg):
# 内部会为当前线程创建一个空间用于存储:phone=自己的值
v.phone = arg
time.sleep(2)
print(v.phone,arg) # 去当前线程自己空间取值
for i in range(10):
t =threading.Thread(target=func,args=(i,))
t.start()
三. 线程池
from concurrent.futures import ThreadPoolExecutor
import time
def task(a1,a2):
time.sleep(2)
print(a1,a2)
# 创建了一个线程池(最多5个线程)
pool = ThreadPoolExecutor(5)
for i in range(40):
# 去线程池中申请一个线程,让线程执行task函数。
pool.submit(task,i,8)
四. 生产者消费者模型
三部件:
生产者
队列,先进先出
扩展: 栈,后进先出
消费者
生产者消费者模型解决了不用一直等待的问题。
示例:
import time
import queue
import threading
q = queue.Queue() # 线程安全
def producer(id):
"""
生产者
:return:
"""
while True:
time.sleep(2)
q.put('包子')
print('厨师%s 生产了一个包子' %id )
for i in range(1,4):
t = threading.Thread(target=producer,args=(i,))
t.start()
def consumer(id):
"""
消费者
:return:
"""
while True:
time.sleep(1)
v1 = q.get()
print('顾客 %s 吃了一个包子' % id)
for i in range(1,3):
t = threading.Thread(target=consumer,args=(i,))
t.start()