线程和进程的区别:
进程之间切换非常消耗资源
线程之间切换相对来说节省资源
使用线程的场景:
多进程和多线程的数据共享的区别
# 多进程 最后执行打印结果两个100
from multiprocessing import Process
def func1():
global g
g = 0
if __name__ == '__main__':
g = 100
print(g)
p = Process(target=func1)
p.start()
p.join()
print(g)
# 多线程 最后执行打印结果两个第一个100,第二个0
from threading import Thread def func1(): global g g = 0 if __name__ == '__main__': g = 100 print(g) t = Thread(target=func1) t.start() t.join() print(g)
全局解释锁GIL锁住的不是线程共享的数据,而是所有的进程。
虽然有了全局解释锁GIL,但是还不是最安全的,因为比如以下情况:
这种情况下我想着线程有了全局解释锁,最后结果应该是0,打印出来结果却是9,因为中间我们人为让程序睡眠了一秒(真实情况下可能由于时间片轮转到了下一个线程),
所以这个时候就需要加锁了。
科学家吃面引出来的死锁问题:
from threading import Thread, Lock
import time
noodle_lock = Lock()
fork_look = Lock()
def eat1(name):
noodle_lock.acquire()
print("{}拿到面了".format(name))
fork_look.acquire()
print("{}拿到叉子了".format(name))
print("{}开始吃面了".format(name))
noodle_lock.release()
fork_look.release()
def eat2(name):
fork_look.acquire()
print("{}拿到叉子了".format(name))
time.sleep(1)
noodle_lock.acquire()
print("{}拿到面了".format(name))
print("{}开始吃面了".format(name))
fork_look.release()
noodle_lock.release()
if __name__ == '__main__':
Thread(target=eat1, args=("alex", )).start()
Thread(target=eat2, args=("egon", )).start()
Thread(target=eat1, args=("nezha", )).start()
Thread(target=eat2, args=("bossjin", )).start()
科学家吃面引出来的递归锁问题:
from threading import Thread, RLock
import time
fork_look = noodle_lock = RLock()
def eat1(name):
noodle_lock.acquire()
print("{}拿到面了".format(name))
fork_look.acquire()
print("{}拿到叉子了".format(name))
print("{}开始吃面了".format(name))
noodle_lock.release()
fork_look.release()
def eat2(name):
fork_look.acquire()
print("{}拿到叉子了".format(name))
time.sleep(1)
noodle_lock.acquire()
print("{}拿到面了".format(name))
print("{}开始吃面了".format(name))
fork_look.release()
noodle_lock.release()
if __name__ == '__main__':
Thread(target=eat1, args=("alex", )).start()
Thread(target=eat2, args=("egon", )).start()
Thread(target=eat1, args=("nezha", )).start()
Thread(target=eat2, args=("bossjin", )).start()
同一个进程或线程中用到两把或两把以上锁的时候,就容易产生死锁现象,这个时候把互斥锁改为递归锁,就能规避这种情况。
RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。
进程中的数据本身就不共享,所以很少会用到加锁的情况,而线程则是数据共享的,所以需要加锁
线程中的事件例子:
from threading import Thread, Event
import random
import time
e = Event()
def check_web():
time.sleep(random.randint(3, 6))
e.set()
def conn_db():
count = 1
while count < 4:
e.wait(1)
if e.is_set():
print("连接数据库成功")
break
else:
print("第{}次连接数据库失败".format(count))
count += 1
else:
raise TimeoutError("连接数据库超时")
if __name__ == '__main__':
Thread(target=check_web).start()
Thread(target=conn_db).start()
线程中的定时器
from threading import Thread, Timer
def func():
print("五秒后的线程开启")
if __name__ == '__main__':
Timer(interval=5, function=func).start()
线程池中的队列:
# 多线程中的数据已经是共享的了,为什么还要用队列?
# 比如a线程要修改共享的一个列表中的一个元素,刚拿回来数据准备修改,时间片到了b线程,
# 这个时候b又去修改这个元素,就会造成数据的不安全,这个时候可以想到应该加锁,
# 而队列中就是加好锁了,供我们使用。
import queue
q = queue.Queue() #队列,先进先出
q.put(1)
q.put(2)
print(q.get())
q = queue.LifoQueue() #栈,先进后出
q.put(1)
q.put(2)
print(q.get())
q = queue.PriorityQueue() #优先级队列
q.put((10, "a"))
q.put((1, "b"))
q.put((8, "c"))
print(q.get())
线程池
# 线程池里面的线程个数最多不要超过cpu个数乘以五
from concurrent.futures import ThreadPoolExecutor
import time
def func(n):
print(n)
time.sleep(1)
return n * n
if __name__ == '__main__':
pool = ThreadPoolExecutor(max_workers=5)
for i in range(1, 21):
p = pool.submit(func, i)
pool.shutdown() #shutdown起到了close和join两个效果,close不让往线程池中扔任务,join等待线程池中的任务执行完毕
print("主线程")