1、介绍
python多线程详解
1.1 什么是线程?
线程也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。
线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所
拥有的全部资源。一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行
**1.2 为什么要使用多线程?**
线程在程序中是独立的、并发的执行流。与分隔的进程相比,进程中线程之间的隔离程度要小,它们共享内存、文件句柄
和其他进程应有的状态。
因为线程的划分尺度小于进程,使得多线程程序的并发性高。进程在执行过程之中拥有独立的内存单元,而多个线程共享
内存,从而极大的提升了程序的运行效率。
线程比进程具有更高的性能,这是由于同一个进程中的线程都有共性,多个线程共享一个进程的虚拟空间。线程的共享环境
包括进程代码段、进程的共有数据等,利用这些共享的数据,线程之间很容易实现通信。
操作系统在创建进程时,必须为改进程分配独立的内存空间,并分配大量的相关资源,但创建线程则简单得多。因此,使用多线程
来实现并发比使用多进程的性能高得要多。
**1.3 总结起来,使用多线程编程具有如下几个优点:**
进程之间不能共享内存,但线程之间共享内存非常容易。
操作系统在创建进程时,需要为该进程重新分配系统资源,但创建线程的代价则小得多。因此使用多线程来实现多任务并发执行比使用多进程的效率高
python语言内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了python的多线程编程。
2、python线程的创建方式
2.1 普通的创建方式
'''
普通创建方式
'''
def run(n):
print('task',n)
time.sleep(1)
print('2s')
time.sleep(1)
print('1s')
time.sleep(1)
print('0s')
time.sleep(1)
if __name__ == '__main__':
t1 = threading.Thread(target=run,args=('t1',)) # target是要执行的函数名(不是函数),args是函数对应的参数,以元组的形式存在
t2 = threading.Thread(target=run,args=('t2',))
t1.start()
t2.start()
传参需要注意,建议在没有参数的时候,args=(),有参数的时候在最后一个参数后面添加一个,
,具体是因为不写会报错,百度解释是这里迭代的过程可能遍历参数,避免报错,建议在最后一个参数后面添加,
2.2 自定义线程,对线程进行重写
'''
自定义线程:继承threading.Thread来定义线程类,其本质是重构Thread类中的run方法
'''
class MyThread(threading.Thread):
def __init__(self,n):
super(MyThread,self).__init__() #重构run函数必须写
self.n = n
def run(self):
print('task',self.n)
time.sleep(1)
print('2s')
time.sleep(1)
print('1s')
time.sleep(1)
print('0s')
time.sleep(1)
if __name__ == '__main__':
t1 = MyThread('t1')
t2 = MyThread('t2')
t1.start()
t2.start()
2.3 守护线程
'''
守护线程
下面这个例子,这里使用setDaemon(True)把所有的子线程都变成了主线程的守护线程,
因此当主线程结束后,子线程也会随之结束,所以当主线程结束后,整个程序就退出了。
所谓’线程守护’,就是主线程不管该线程的执行情况,只要是其他子线程结束且主线程执行完毕,主线程都会关闭。也就是说:主线程不等待该守护线程的执行完再去关闭。
'''
def run(n):
print('task',n)
time.sleep(1)
print('3s')
time.sleep(1)
print('2s')
time.sleep(1)
print('1s')
if __name__ == '__main__':
t=threading.Thread(target=run,args=('t1',))
t.setDaemon(True)
t.start()
print('end')
'''
通过执行结果可以看出,设置守护线程之后,当主线程结束时,子线程也将立即结束,不再执行
'''
2.4 主线程等待子线程结束
'''
主线程等待子线程结束
为了让守护线程执行结束之后,主线程再结束,我们可以使用join方法,让主线程等待子线程执行
'''
def run(n):
print('task',n)
time.sleep(2)
print('5s')
time.sleep(2)
print('3s')
time.sleep(2)
print('1s')
if __name__ == '__main__':
t=threading.Thread(target=run,args=('t1',))
t.setDaemon(True) #把子线程设置为守护线程,必须在start()之前设置
t.start()
t.join() #设置主线程等待子线程结束
print('end')
2.5 线程使用全局变量
'''
多线程共享全局变量
线程时进程的执行单元,进程时系统分配资源的最小执行单位,所以在同一个进程中的多线程是共享资源的
'''
g_num = 100
def work1():
global g_num
for i in range(3):
g_num+=1
print('in work1 g_num is : %d' % g_num)
def work2():
global g_num
print('in work2 g_num is : %d' % g_num)
if __name__ == '__main__':
t1 = threading.Thread(target=work1)
t1.start()
time.sleep(1)
t2=threading.Thread(target=work2)
t2.start()
2.6 线程上锁
'''
由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据,
所以出现了线程锁,即同一时刻允许一个线程执行操作。线程锁用于锁定资源,可以定义多个锁,像下面的代码,当需要独占
某一个资源时,任何一个锁都可以锁定这个资源,就好比你用不同的锁都可以把这个相同的门锁住一样。
由于线程之间是进行随机调度的,如果有多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期,
我们因此也称为“线程不安全”。
为了防止上面情况的发生,就出现了互斥锁(Lock)
'''
# def work():
# global n
# lock.acquire()
# temp = n
# time.sleep(0.1)
# n = temp-1
# lock.release()
#
#
# if __name__ == '__main__':
# lock = Lock()
# n = 100
# l = []
# for i in range(100):
# p = Thread(target=work)
# l.append(p)
# p.start()
# for p in l:
# p.join()
'''
递归锁:RLcok类的用法和Lock类一模一样,但它支持嵌套,在多个锁没有释放的时候一般会使用RLock类
'''
# def func(lock):
# global gl_num
# lock.acquire()
# gl_num += 1
# time.sleep(1)
# print(gl_num)
# lock.release()
#
#
# if __name__ == '__main__':
# gl_num = 0
# lock = threading.RLock()
# for i in range(10):
# t = threading.Thread(target=func,args=(lock,))
# t.start()
'''
信号量(BoundedSemaphore类)
互斥锁同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据,比如厕所有3个坑,
那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去
'''
# def run(n,semaphore):
# semaphore.acquire() #加锁
# time.sleep(3)
# print('run the thread:%s\n' % n)
# semaphore.release() #释放
#
#
# if __name__== '__main__':
# num=0
# semaphore = threading.BoundedSemaphore(5) #最多允许5个线程同时运行
# for i in range(22):
# t = threading.Thread(target=run,args=('t-%s' % i,semaphore))
# t.start()
# while threading.active_count() !=1:
# pass
# else:
# print('----------all threads done-----------')
2.7
'''
python线程的事件用于主线程控制其他线程的执行,事件是一个简单的线程同步对象,其主要提供以下的几个方法:
clear将flag设置为 False
set将flag设置为 True
is_set判断是否设置了flag
wait会一直监听flag,如果没有检测到flag就一直处于阻塞状态
事件处理的机制:全局定义了一个Flag,当Flag的值为False,那么event.wait()就会阻塞,当flag值为True,
那么event.wait()便不再阻塞
'''
event = threading.Event()
def lighter():
count = 0
event.set() #初始者为绿灯
while True:
if 5 < count <=10:
event.clear() #红灯,清除标志位
print("\33[41;lmred light is on...\033[0m]")
elif count > 10:
event.set() #绿灯,设置标志位
count = 0
else:
print('\33[42;lmgreen light is on...\033[0m')
time.sleep(1)
count += 1
def car(name):
while True:
if event.is_set(): #判断是否设置了标志位
print('[%s] running.....'%name)
time.sleep(1)
else:
print('[%s] sees red light,waiting...'%name)
event.wait()
print('[%s] green light is on,start going...'%name)
# startTime = time.time()
light = threading.Thread(target=lighter,)
light.start()
car = threading.Thread(target=car,args=('MINT',))
car.start()
endTime = time.time()
# print('用时:',endTime-startTime)
'''
GIL 全局解释器
在非python环境中,单核情况下,同时只能有一个任务执行。多核时可以支持多个线程同时执行。但是在python中,无论有多少个核
同时只能执行一个线程。究其原因,这就是由于GIL的存在导致的。
GIL的全程是全局解释器,来源是python设计之初的考虑,为了数据安全所做的决定。某个线程想要执行,必须先拿到GIL,我们可以
把GIL看做是“通行证”,并且在一个python进程之中,GIL只有一个。拿不到线程的通行证,并且在一个python进程中,GIL只有一个,
拿不到通行证的线程,就不允许进入CPU执行。GIL只在cpython中才有,因为cpython调用的是c语言的原生线程,所以他不能直接操
作cpu,而只能利用GIL保证同一时间只能有一个线程拿到数据。而在pypy和jpython中是没有GIL的
python在使用多线程的时候,调用的是c语言的原生过程。
'''
'''
python针对不同类型的代码执行效率也是不同的
1、CPU密集型代码(各种循环处理、计算等),在这种情况下,由于计算工作多,ticks技术很快就会达到阀值,然后出发GIL的
释放与再竞争(多个线程来回切换当然是需要消耗资源的),所以python下的多线程对CPU密集型代码并不友好。
2、IO密集型代码(文件处理、网络爬虫等设计文件读写操作),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,
造成不必要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序的执行
效率)。所以python的多线程对IO密集型代码比较友好。
'''
'''
主要要看任务的类型,我们把任务分为I/O密集型和计算密集型,而多线程在切换中又分为I/O切换和时间切换。如果任务属于是I/O密集型,
若不采用多线程,我们在进行I/O操作时,势必要等待前面一个I/O任务完成后面的I/O任务才能进行,在这个等待的过程中,CPU处于等待
状态,这时如果采用多线程的话,刚好可以切换到进行另一个I/O任务。这样就刚好可以充分利用CPU避免CPU处于闲置状态,提高效率。但是
如果多线程任务都是计算型,CPU会一直在进行工作,直到一定的时间后采取多线程时间切换的方式进行切换线程,此时CPU一直处于工作状态,
此种情况下并不能提高性能,相反在切换多线程任务时,可能还会造成时间和资源的浪费,导致效能下降。这就是造成上面两种多线程结果不能的解释。
结论:I/O密集型任务,建议采取多线程,还可以采用多进程+协程的方式(例如:爬虫多采用多线程处理爬取的数据);对于计算密集型任务,python此时就不适用了。
'''
3、python线程结合摄像头的一个简单使用场景
话不多说:直接上代码
功能是,调用训练好的口罩识别模型,结合百度ai人脸识别,实现的一个实时口罩人脸检测:
尾部注释,是个人的一个思考过程,和最终实现是有所出入的。
效果:
import base64
import threading
import time
from queue import Queue
import cv2
import requests
import os
import numpy as np
import tflearn
from PIL import Image
from tflearn import input_data, conv_2d, max_pool_2d, fully_connected, dropout, regression
IMAGE_SIZE = 50
VIDEO_PATH = "WIN_20210810_23_07_28_Pro.mp4"
#######################################################
#
# 卷积层定义和模型加载
#
#######################################################
# 搭建神经网络
# 输入层
# 图片大小:50*50
indata = input_data([None, IMAGE_SIZE, IMAGE_SIZE, 1], name='input')
# 第一卷积层
# 输入的数据:indata;32是输出的深度;卷积核的大小:5*5;activation激活函数 :relu
conv1 = conv_2d(indata, 32, 5, activation='relu')
# 第一池化层
conv_pool1 = max_pool_2d(conv1, 2)
# 输入的数据:indata;64是输出的深度;卷积核的大小:5*5;activation激活函数 :relu
conv2 = conv_2d(conv_pool1, 64, 5, activation='relu')
# 第一池化层
conv_pool2 = max_pool_2d(conv2, 2)
# 输入的数据:indata;64是输出的深度;卷积核的大小:5*5;activation激活函数 :relu
conv3 = conv_2d(conv_pool2, 64, 5, activation='relu')
# 第一池化层
conv_pool3 = max_pool_2d(conv3, 2)
# 输入的数据:indata;64是输出的深度;卷积核的大小:5*5;activation激活函数 :relu
conv4 = conv_2d(conv_pool3, 128, 5, activation='relu')
# 第一池化层
conv_pool4 = max_pool_2d(conv4, 2)
# 输入的数据:indata;64是输出的深度;卷积核的大小:5*5;activation激活函数 :relu
conv5 = conv_2d(conv_pool4, 128, 5, activation='relu')
# 第一池化层
conv_pool5 = max_pool_2d(conv5, 2)
# 输入的数据:indata;64是输出的深度;卷积核的大小:5*5;activation激活函数 :relu
conv6 = conv_2d(conv_pool5, 128, 5, activation='relu')
# 第一池化层
conv_pool6 = max_pool_2d(conv6, 2)
# 输入的数据:indata;64是输出的深度;卷积核的大小:5*5;activation激活函数 :relu
conv7 = conv_2d(conv_pool6, 128, 5, activation='relu')
# 第一池化层
conv_pool7 = max_pool_2d(conv7, 2)
# 输入的数据:indata;64是输出的深度;卷积核的大小:5*5;activation激活函数 :relu
conv8 = conv_2d(conv_pool7, 128, 5, activation='relu')
# 第一池化层
conv_pool8 = max_pool_2d(conv8, 2)
# 输入的数据:indata;64是输出的深度;卷积核的大小:5*5;activation激活函数 :relu
conv9 = conv_2d(conv_pool8, 128, 5, activation='relu')
# 第一池化层
conv_pool9 = max_pool_2d(conv9, 2)
# 第一个全连层 特征汇总
fully_layer = fully_connected(conv_pool8, 1024, activation='relu')
# 删除神经元的个数
fully_layer1 = dropout(fully_layer, 0.7)
# 输出层做分类 2分类关系
fully_layer2 = fully_connected(fully_layer1, 2, activation='softmax')
# 损失函数
model_net = regression(fully_layer2, optimizer="adam", loss="categorical_crossentropy",
learning_rate=0.0001, name='model_net')
# 模型的构建,和knn一样
model = tflearn.DNN(model_net, tensorboard_dir='log')
model.load('model/mask-ummask.model')
#######################################################
#######################################################
#
# 调用百度ai进行图像初步识别和处理
#
#######################################################
class CameraAi():
def imageSerch(self, path):
"""
调用百度AI进行识别检索图像是否有人脸
:return:
"""
request_url = "https://aip.baidubce.com/rest/2.0/face/v3/detect"
f = open(path, 'rb')
# base64编码:去掉编码头
self.img_t = base64.b64encode(f.read())
params = {
"image": self.img_t,
"image_type": "BASE64",
"face_field": "faceshape,facetype",
"max_face_num": "13"
}
access_token = self.getAccessToken('q9T28B3GGcH3B7GA8YTP6o4v', 'OQvcjkbYWx3UcFWYgsoLhB8odxTpdD6e')
request_url = request_url + "?access_token=" + access_token
headers = {'content-type': 'application/json'}
response = requests.post(request_url, data=params, headers=headers)
if response:
content = response.json()
# print(content)
if content['result'] is None:
return 0
return content['result']
def getAccessToken(self, client_id, client_secret):
"""
获取Token
:param client_id: 官网获取的AK
:param client_secret: 官网获取的SK
:return:
"""
host = host = 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id={}&client_secret={}'.format(
client_id, client_secret)
response = requests.get(host)
if response:
# print("token:" + response.json()["access_token"])
return response.json()["access_token"]
else:
print("error:", end=" ")
# print(response.json())
#######################################################
"""
图片检测前的一个预处理
"""
# 预测数据图片处理
def ImagePre(img_name):
img = cv2.imread(img_name, cv2.IMREAD_GRAYSCALE)
# 图片大小的设置
if img is not None:
img = cv2.resize(img, (IMAGE_SIZE, IMAGE_SIZE))
data = img.reshape(-1, IMAGE_SIZE, IMAGE_SIZE, 1)
return data
def image_get(queue1, path):
cap = cv2.VideoCapture(path)
while True:
queue1.put(cap.read()[1])
if queue1.qsize() > 20:
continue
else:
time.sleep(0.01)
def image_deal(frame):
tempimage = "temp_01.jpg"
cv2.imwrite(tempimage, frame)
cam = CameraAi()
contents = cam.imageSerch(tempimage)
print(contents)
faceNum = contents['face_num']
img = cv2.imread(tempimage)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
for i in range(faceNum):
x = int(contents['face_list'][i]['location']['left'])
y = int(contents['face_list'][i]['location']['top'])
w = int(contents['face_list'][i]['location']['width'])
h = int(contents['face_list'][i]['location']['height'])
print('开始处理图片切割, 请稍候...')
box = (x, y, x + w, y + h)
imglabel = Image.open("temp_01.jpg")
imglabel.crop(box).save(os.path.join(".", "temp.jpg")) # KeyError: 'JPG'
pred = model.predict(ImagePre("temp.jpg"))
print(pred)
res = np.argmax(pred)
if res == 0:
flag = 1
print("佩戴口罩")
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
elif res == 1:
flag = 0
print("未佩戴口罩")
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 2)
return img
def image_show(queue1):
cv2.namedWindow("Video", flags=cv2.WINDOW_FREERATIO)
while True:
frame = queue1.get()
frame = image_deal(frame)
cv2.imshow("Video", frame)
cv2.waitKey(1)
def run_single_camera(path):
# 创建两个队列
queue1 = Queue(maxsize=5)
# 创建多个线程
# 线程1
t1 = threading.Thread(target=image_get, args=(queue1, path,))
t2 = threading.Thread(target=image_show, args=(queue1,))
t1.start()
t2.start()
def run(path):
run_single_camera(path)
# 结合视频的一个处理流程,这里选择使用的是FIFO的队列
if __name__ == "__main__":
run(VIDEO_PATH)
# 整理一下视频的一个处理过程
# 利用opencv进行视频信息的一个读取,将读取的视频信息
# 将读取的视频插入到FIFO队列当中
# 从队列中获取图片,并对图片做处理,并显示在界面上
"""
import multiprocessing
import os
print("当前进程ID:",os.getpid())
# 定义一个函数,准备作为新进程的 target 参数
def action(name,*add):
print(name)
for arc in add:
print("%s --当前进程%d" % (arc,os.getpid()))
if __name__=='__main__':
#定义为进程方法传入的参数
my_tuple = ("http://c.biancheng.net/python/",\
"http://c.biancheng.net/shell/",\
"http://c.biancheng.net/java/")
#设置进程启动方式
multiprocessing.set_start_method('spawn')
#创建子进程,执行 action() 函数
my_process = multiprocessing.Process(target = action, args = ("my_process进程",*my_tuple))
#启动子进程
my_process.start()
"""
"""
图片的处理流程
1、获取图片
2、对图片进行调用百度ai进行一个识别
识别是否有人脸,找出图片人脸的一个位置做输出
将指定位置的图片截取出来
对截取出来的图片做一个识别
对识别结果对图片做处理,处理后的保存会队列中
3、图片显示
将处理完之后的队列进行一个输出
///
处理框架
def image_deal(queue1, queue2):
while True:
print("##########################")
time.sleep(2)
def image_show(queue2):
while True:
print("**************************")
time.sleep(3)
def run_single_camera(path):
# 创建两个队列
queue1 = Queue(maxsize=50)
queue2 = Queue(maxsize=50)
# 创建多个线程
# 线程1
t1 = threading.Thread(target=image_get, args=(queue1,))
t2 = threading.Thread(target=image_deal, args=(queue1, queue2))
t3 = threading.Thread(target=image_show, args=(queue2,))
t1.start()
t2.start()
t3.start()
def run(path):
run_single_camera(path)
# 结合视频的一个处理流程,这里选择使用的是FIFO的队列
if __name__ == "__main__":
run(VIDEO_PATH)
将二三线程进行一个合并
改为一个队列FIFO进行使用
///
"""
"""
python中的Queue(队列详解)
python 中的队列分为两种:
1、线程Queue,也就是普通的Queue
2、进程Queue,在多线程和多进程中去考虑使用
Queue的种类:
1、FIFO
Queue.Queue(maxsize=0)
FIFO即First in First Out,先进先出。
Queue提供了一个基本的FIFO容器,使用方法很简单,maxsize是个整数,
指明了队列中能存放的数据个数的上限。一旦达到上限,插入会导致阻塞,
直到队列中的数据被消费掉。如果maxsize小于或者等于0,队列大小没有限制。
2、LIFO
Queue.LifoQueue(maxsize=0)
LIFO即Last in First Out,后进先出。与栈的类似,使用也很简单,maxsize用法同上
3、priority
class Queue.PriorityQueue(maxsize=0)
构造一个优先队列。maxsize用法同上。
基本方法:
Queue.Queue(maxsize=0) FIFO, 如果maxsize小于1就表示队列长度无限
Queue.LifoQueue(maxsize=0) LIFO, 如果maxsize小于1就表示队列长度无限
Queue.qsize() 返回队列的大小
Queue.empty() 如果队列为空,返回True,反之False
Queue.full() 如果队列满了,返回True,反之False
Queue.get([block[, timeout]]) 读队列,timeout等待时间
Queue.put(item, [block[, timeout]]) 写队列,timeout等待时间
Queue.queue.clear() 清空队列
其他:
task_done()
意味着之前入队的一个任务已经完成。由队列的消费者线程调用。
每一个get()调用得到一个任务,接下来的task_done()调用告诉队列该任务已经处理完毕。
如果当前一个join()正在阻塞,它将在队列中的所有任务都处理完时恢复执行
(即每一个由put()调用入队的任务都有一个对应的task_done()调用)。
join()
阻塞调用线程,直到队列中的所有任务被处理掉。
只要有数据被加入队列,未完成的任务数就会增加。当消费者线程调用task_done()
(意味着有消费者取得任务并完成任务),
未完成的任务数就会减少。当未完成的任务数降到0,join()解除阻塞。
"""
补充:python队列知识
一、队列(Queue)
Python的Queue模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue。这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的同步。
常用方法:
Queue.qsize() 返回队列的大小
Queue.empty() 如果队列为空,返回True,反之False
Queue.full() 如果队列满了,返回True,反之False,Queue.full 与 maxsize 大小对应
Queue.get([block[, timeout]])获取队列,timeout等待时间
Queue.get_nowait() 相当于Queue.get(False),非阻塞方法
Queue.put(item) 写入队列,timeout等待时间
Queue.task_done() 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号。每个get()调用得到一个任务,接下来task_done()调用告诉队列该任务已经处理完毕。
Queue.join() 实际上意味着等到队列为空,再执行别的操作
简单示例代码:
from Queue import Queue,LifoQueue,PriorityQueue
#先进先出队列
q=Queue(maxsize=5)
#后进先出队列
lq=LifoQueue(maxsize=6)
#优先级队列
pq=PriorityQueue(maxsize=5)
for i in range(5):
q.put(i)
lq.put(i)
pq.put(i)
print "先进先出队列:%s;是否为空:%s;多大,%s;是否满,%s" %(q.queue,q.empty(),q.qsize(),q.full())
print "后进先出队列:%s;是否为空:%s;多大,%s;是否满,%s" %(lq.queue,lq.empty(),lq.qsize(),lq.full())
print "优先级队列:%s;是否为空:%s,多大,%s;是否满,%s" %(pq.queue,pq.empty(),pq.qsize(),pq.full())
print q.get(),lq.get(),pq.get()
print "先进先出队列:%s;是否为空:%s;多大,%s;是否满,%s" %(q.queue,q.empty(),q.qsize(),q.full())
print "后进先出队列:%s;是否为空:%s;多大,%s;是否满,%s" %(lq.queue,lq.empty(),lq.qsize(),lq.full())
print "优先级队列:%s;是否为空:%s,多大,%s;是否满,%s" %(pq.queue,pq.empty(),pq.qsize(),pq.full())
先进先出队列:deque([0, 1, 2, 3, 4]);是否为空:False;多大,5;是否满,True
后进先出队列:[0, 1, 2, 3, 4];是否为空:False;多大,5;是否满,False
优先级队列:[0, 1, 2, 3, 4];是否为空:False,多大,5;是否满,True
0 4 0
先进先出队列:deque([1, 2, 3, 4]);是否为空:False;多大,4;是否满,False
后进先出队列:[0, 1, 2, 3];是否为空:False;多大,4;是否满,False
优先级队列:[1, 3, 2, 4];是否为空:False,多大,4;是否满,False
双边队列,示例代码如下:
from Queue import deque
dq=deque(['a','b'])
dq.append('c')
print dq
print dq.pop()
print dq
print dq.popleft()
print dq
dq.appendleft('d')
print dq
print len(dq)
deque(['a', 'b', 'c'])
c
deque(['a', 'b'])
a
deque(['b'])
deque(['d', 'b'])
2
生产者消费者模式
生产者消费者模式并不是GOF提出的众多模式之一,但它依然是开发同学编程过程中最常用的一种模式
生产者模块儿负责产生数据,放入缓冲区,这些数据由另一个消费者模块儿来从缓冲区取出并进行消费者相应的处理。该模式的优点在于:
解耦:缓冲区的存在可以让生产者和消费者降低互相之间的依赖性,一个模块儿代码变化,不会直接影响另一个模块儿
并发:由于缓冲区,生产者和消费者不是直接调用,而是两个独立的并发主体,生产者产生数据之后把它放入缓冲区,就继续生产数据,不依赖消费者的处理速度
采用生产者消费者模式开发的Python多线程
在Python中,队列是最常用的线程间的通信方法,因为它是线程安全的,自带锁。而Condition等需要额外加锁的代码操作,在编程对死锁现象要很小心,Queue就不用担心这个问题。
4、总结
多线程
多线程处理可以同时运行多个线程。由于多线程应用程序将程序划分成多个独立的任务,因此可以在以下方面显著提高性能:
(1)多线程技术使程序的响应速度更快 ,因为用户界面可以在进行其它工作的同时一直处于活动状态;
(2)当前没有进行处理的任务时可以将处理器时间让给其它任务;
(3)占用大量处理时间的任务可以定期将处理器时间让给其它任务;
(4)可以随时停止任务;
(5)可以分别设置各个任务的优先级以优化性能。
是否需要创建多个线程取决于各种因素。在以下情况下,最适合采用多线程处理:
(1)耗时或大量占用处理器的任务阻塞用户界面操作;
(2)各个任务必须等待外部资源(如远程文件或 Internet连接)。
同样的 ,多线程也存在许多缺点 ,在考虑多线程时需要进行充分的考虑。
多线程的主要缺点包括:
(1)等候使用共享资源时造成程序的运行速度变慢。这些共享资源主要是独占性的资源 ,如打印机等。
(2)对线程进行管理要求额外的 CPU开销。线程的使用会给系统带来上下文切换的额外负担。
当这种负担超过一定程度时,多线程的特点主要表现在其缺点上,比如用独立的线程来更新数组内每个元素。
(3)线程的死锁。即较长时间的等待或资源竞争以及死锁等多线程症状。
(4)对公有变量的同时读或写。当多个线程需要对公有变量进行写操作时,后一个线程往往会修改掉前一个线程存放的数据,从而使前一个线程的参数被修改;
另外 ,当公用变量的读写操作是非原子性时,在不同的机器上,中断时间的不确定性,会导致数据在一个线程内的操作产生错误,从而产生莫名其妙的错误,而这种错误是程序员无法预知的。
5、资源
百度AI使用介绍
模型训练
模型测试
模型使用
配套资源和作业学习文档