线程微讲解
线程微讲解
1.消息队列
队列:先进先出(使用频率很高)
堆栈:先进后出(特定场景下才会使用)
以后我们会直接使用别人封装好的消息队列来实现各种数据传输,我们先来看一个别人封装好的消息队列,也就是内置队列。
from multiprocessing import Queue
q = Queue(5) # 自定义队列长度
# 朝队列中存放数据
q.put(1)
q.put(2)
q.put(3)
print(q.full()) # Fales 判断队列是否满了,满了就返回True,否则就返回Fales
q.put(4)
q.put(5)
print(q.full()) # True
# q.put(6) # 超出最大长度,原地阻塞等待队列出现空位
print(q.get())
print(q.get())
print(q.get())
print(q.empty()) # False 判断队列是否空了,空了就返回True,否则就返回False
print(q.get())
print(q.get())
print(q.empty()) # True
# print(q.get()) # 队列中没有值了还要继续取值的话就会原地等待队列添加新的值
# print(q.get_nowait()) # 队列中如果没有值了就会直接报错
'''full()、empty()、get_nowait()这三种方法在并发的场景下是不能精准使用的,之所以介绍队列是因为它可以支持进程间数据通信'''
2.IPC机制(进程间通信)
# 进程间通信有两种方式
1.主进程与子进程数据交互
2.两个子进程数据交互
# 进程间通信的本质
进程间通信的本质就是不同内存空间中的进程数据交互。
# 代码演示
from multiprocessing import Process,Queue
def producer(q):
print('子进程producer从队列中取值',q.get()) # 第一次取值,取出的是111
q.put('子进程producer从队列中添加值') # 再次添加值
def consumer(q):
print('子进程consumer从队列中取值',q.get()) # 这次取值取出的是第二次添加的值,因为第一次添加的已经被取出了
if __name__ == '__main__':
q = Queue()
p = Process(target=producer,args=(q,))
p1 = Process(target=consumer,args=(q,))
p.start()
p1.start()
q.put(111) # 往队列中先添加一个值
print('主进程')
3.生产者消费者模型
# 生产者
制作、产生数据(负责生产)
# 消费者
处理、回收数据(负责消费)
'''用进程来演示,至少需要两个进程,还需要一个媒介,也就是消息队列,该模型需要考虑的问题就是供需平衡的问题,也就是生产
的要跟得上消费的,消费的要追得上生产的'''
# 代码演示
from multiprocessing import Process,Queue,JoinableQueue
import time
import random
def producer(name,food,q):
for i in range(5):
data = f'{name}生产的{food}{i}'
print(data)
time.sleep(random.randint(1,3)) # 模拟产生过程
q.put(data)
def consumer(name,q):
while True:
food = q.get()
if food == None:
print('没饭吃了')
break
time.sleep(random.random())
print(f'{name}吃了{food}')
q.task_done() # 每次吃完数据必须给队列返回一个信息
if __name__ == '__main__':
# q = Queue()
q = JoinableQueue()
p1 = Process(target=producer,args=('oscar','糖醋排骨',q))
p2 = Process(target=producer,args=('jason','土豆炖排骨',q))
c1 = Process(target=consumer,args=('tony',q))
c2 = Process(target=consumer,args=('tom',q))
c1.daemon = True
c2.daemon = True
p1.start()
p2.start()
c1.start()
c2.start()
# 生产者生产完所有数据之后,往队列中添加结束的信号
p1.join()
p2.join()
# q.put(None) # 结束信号的个数要跟消费者个数一样
# q.put(None) # 结束信号的个数要跟消费者个数一样
'''队列中其实已经自己加了锁,所以多进程取值也不会冲突,并且取走就没了'''
q.join() # 等待队列中数据全部被取出,一定要让生产者全部结束才能判断正确
'''执行完上述的join方法表示消费者也已经消费完数据了'''
4.线程理论
# 什么线程
进程:资源单位
线程:执行单位
'''进程仅仅是在内存中开辟一块空间(提供线程工作所需的资源),线程是真正被CPU执行的,线程需要的资源就跟所在进程要,所
以一个进程中至少有一个线程'''
# 为什么有线程
因为开设线程的消耗远远小于进程,开进程需要申请内存空间,还需要拷贝代码,而一个进程内就可以开设多个线程,无
需申请内存空间,也无需拷贝代码,因为一个进程内的多个线程数据是共享的。
5.开设线程的两种方式
from threading import Thread
import time
# def task(name):
# print(f'{name}刚刚来')
# time.sleep(3)
# print(f'{name}又走了')
#
# # 创建线程无需在__main__子代码中,但是为了同一进程,还是习惯写在里面
# if __name__ == '__main__':
# t = Thread(target=task,args=('oscar',))
# t.start()
# print('主线程')
class MyThread(Thread):
def __init__(self,username):
super().__init__()
self.username = username
def run(self):
print(f'{self.username}刚刚来了')
time.sleep(3)
print(f'{self.username}又走了')
if __name__ == '__main__':
t = MyThread('oscar')
t.start()
print('主线程')
6.线程join方法
from threading import Thread
import time
def task(name):
print(f'{name}刚刚来的')
time.sleep(3)
print(f'{name}又走了')
if __name__ == '__main__':
t = Thread(target=task,args=('oscar',))
t.start()
t.join() # 主线程等待子线程结束之后才会结束
print('主线程')
'''主线程等待子线程结束之后结束,是因为主线程结束标志着进程的结束,所以主线程等待子线程结束之后结束是为了确保子线程
运行过程中所需要的资源'''
7.同一个进程内的多个线程数据共享
from threading import Thread
s = 1
def stak():
global s
s = 0
if __name__ == '__main__':
t = Thread(target=stak)
t.start()
t.join()
print(s) # 0
8.线程对象属性和方法
# 1.验证一个进程下的多个进程是否真的处于一个进程
# 2.统计进程下活跃的线程数
active_count() # 主线程也算
# 3.获取线程名字
1.current_thread().name
返回的MainThread是主线程,Thread-加数字的是子线程
2.self.name
# 代码演示
from threading import Thread, active_count, current_thread
import os
import time
def task():
time.sleep(1)
print('子线程获取进程号>>>:',os.getpid())
print(current_thread().name)
t = Thread(target=task)
t1 = Thread(target=task)
t.start()
t1.start()
print(active_count())
print(current_thread().name)
print('主线程获取进程号>>>:', os.getpid())
9.守护线程
from threading import Thread
import time
def task(name):
print(f'{name}刚刚来')
time.sleep(3)
print(f'{name}又走了')
if __name__ == '__main__':
t1 = Thread(target=task,args=('oscar',))
t2 = Thread(target=task,args=('jason',))
t1.daemon = True
t1.start()
t2.start()
print('主线程')
'''主线程要等待所有的子线程结束之后才会结束'''
10.GIL全局解释器锁
# 官方文档
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from
executing Python bytecodes at once. This lock is necessary mainly
because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features
have grown to depend on the guarantees that it enforces.)
'''
GIL只存在CPython解释器中,不是python的特征。
GIL是一把互斥锁用于阻止同一个进程下的多个线程同时执行,原因是因为CPython解释器中的垃圾回收机制不是线程安
全的。
GIL是加在CPython解释器上面的互斥锁
同一个进程下的多个线程要想执行必须先抢GIL锁 所以同一个进程下多个线程肯定不能同时运行 即无法利用多核优势。
虽然同一个进程下的多个线程不能同时执行即不能利用多核优势,但是可以开设多个进程,结合实际情况,如果多个任
务都是IO密集型的,那么多线程更有优势(消耗的资源更少),如果多个任务都是计算密集型,那么多线程确实没有优势,但是可
以用多进程。
所有的解释性语言都无法做到同一个进程下多个线程利用多核优势。'''
这里是IT小白陆禄绯,欢迎各位大佬的指点!!!


浙公网安备 33010602011771号