1.JoinableQueue队列
JoinableQueue([maxsize]):这就像是一个Queue对象,但是队列允许项目的使用者通知生成者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。
案例:
from multiprocessing import JoinableQueue # join是等待某个任务完成 able 可以 Queue 队列 # 翻译过来被join的队列 q = JoinableQueue() q.put('123') q.put('456') print('取走一个了%s'% q.get()) q.task_done() # 可以当作是普通的Queue的队列来使用 # task_done方法,是告诉队列这个数据已经被处理完了 # 需要注意的是,此处的处理完的并不是任务全部处理完成, # 而指的是当前取出的数据已经处理完成。 # 所以每一个get 需要对应一个 task_done # task_done 不能超过get的次数,否则会报多次掉用的错误 print('又取走了一个%s'% q.get()) q.task_done() # 如果队列中的值已经被取空了,再次使用get取值会卡死 # 因为他在一直在等待有人往容器里存值 # print('又取走了一个%s'% q.get()) # q.task_done() # join方法,等待队列中的数据被处理完毕, # 一般在使用的时候,join 与 task_done 的次数 == put 的调用次数 q.join() print('结束')
2. 线程:
什么是线程:
线程是操作系统最小的运算调度单位,被包含在进程中,一个线程就是一个固定的执行流程
3.线程于进程的关系 ***** (重点)
线程是不能单独存在的,必须存在进程中,进程是一个资源单位,其包含了运行程序所需的所有资源
线程才是真正的执行单位,没有线程,进程中的资源就无法被利用起来,所以一个进程至少包含一个线程,这个线程就称之为主线程
当我们启动一个程序时,操作系统就会自己为这个程序创建一个主线程,而由程序后期开启的线程,就称之为子线程
4.使用线程:
在使用之前,首先要想一个问题,为什么需要使用线程?
其实目的只有一个,那就是提高效率
如果把进程比喻成车间,那么线程就是车间的流水线。
我们提高效率,当然可以再造一个新车间,那需要把原材料运过去 ,这个过程是非常耗时的
from threading import Thread # 第一种。直接调用Thread模块 def task(): print('子进程开启') print('子进程结束') t1 = Thread(target=task) t1.start() print('主线程结束')
第二种继承Thread,然后覆盖run方法
# 第二种,继承Thread类,覆盖run方法 class MyThread(Thread): def run(self): print('子线程开启') print('子线程结束') t = MyThread() t.start() print('主线程结束')
5.线程的特点
1.创建开销小,不用像进程需要导入数据
2.同一个进程中的多个线程数据共享
3.多个线程之间是平等的,没有父子关系。
案例1:同一进程中的线程数据共享
# 证明同一进程中的线程共享数据 from threading import Thread # 定义一个全局变量 a = 10 def task(): global a print('这是子线程') a = 20 t1 = Thread(target=task) t1.start() t1.join() print(a) # 此处的值已经被修改了,加入是在子进程中,是不会被修改的 # 因为进程之间的数据并不互通
案例二:程序线程的开销小于进程
import os, time from threading import Thread from multiprocessing import Process def task(): pass if __name__ == '__main__': st_time = time.time() ts = [] for i in range(100): # t = Thread(target=task) # 线程输出结果0.01894855499267578 (不固定,因设备而定) t = Process(target=task) # 进程输出结果9.261330604553223 (不固定,因设备而定) t.start() ts.append(t) for t in ts: t.join() # 使用当前时间减去记录的时间会得到开启线程所用的时间 print(time.time() - st_time) print('主线程结束')
6.守护线程
一个线程可以设置为另一个线程的守护线程
from threading import Thread import time def task1(): print('第一子进程开始') time.sleep(2) print('第一子进程结束') def task2(): print('第二子进程开始') time.sleep(1) print('第二子进程结束') t = Thread(target=task1) t.daemon = Thread t.start() t1 = Thread(target=task2) t1.start() print('主线程结束') # 上述案例是将 t 线程作为守护线程 # 运行顺序是: # 第一子进程开始 # 第二子进程开始 # 主线程结束 # 第二子进程结束 # 主线程代码执行完毕后。不会立即结束,会等待其他子线程结束,主线程会等等待非守护线程 (即t2) # 主线程会等待所有非守护线程结束后结束 # 守护线程会等到所有非守护线程结束后结束,如果守护线程已经完成任务,守护线程会立即结束
主线程代码执行完毕后。不会立即结束,会等待其他子线程结束,主线程会等等待非守护线程 (即t2)
主线程会等待所有非守护线程结束后结束
守护线程会等到所有非守护线程结束后结束,如果守护线程已经完成任务,守护线程会立即结束
7.线程的互斥锁
线程中也存在安全问题,
多线程可以并发执行,一旦并发了并且访问了同一个资源就会有问题
解决方案:还是互斥锁
案例:
from threading import Thread,enumerate,Lock import time number = 10 lock = Lock() def task(): global number # 使用互斥锁来锁定资源 lock.acquire() a = number time.sleep(0.1) number = a - 1 # 使用完毕后释放 lock.release() for i in range(10): t = Thread(target=task) t.start() for t in enumerate()[1:]: t.join() print(number) # 只要是并发执行,在访问同一资源的时候就会出现问题,所以与进程一样 # 就需要锁来控制资源
8.死锁问题
from threading import Lock, current_thread, Thread ''' 死锁问题 当程序出现了不止一把锁,分别被不同的线程持有,如果有一个资源要想使用必须同时具备两把锁 这时候程序就会进入无限卡死状态,这就称之为死锁 例如: 要吃饭,必须具备盘子和筷子,但是一个人拿着盘子等筷子。另一个人拿着筷子等盘子 如何避免死锁问题: 锁不要有多个,一个足够 如果真的发生了死锁的问题,必须迫使一方先交出锁 ''' import time lock1 = Lock() lock2 = Lock() def eat1(): lock1.acquire() print('%s抢到了盘子' % current_thread().name) time.sleep(0.5) lock2.acquire() print('%s抢到了筷子' % current_thread().name) print('%s开动了' % current_thread().name) lock2.release() print('%s放下筷子' % current_thread().name) lock1.release() print('%s放下盘子' % current_thread().name) def eat2(): lock2.acquire() print('%s抢到了筷子' % current_thread().name) time.sleep(0.5) lock1.acquire() print('%s抢到了盘子' % current_thread().name) print('%s开动了' % current_thread().name) lock1.release() print('%s放下盘子' % current_thread().name) lock2.release() print('%s放下筷子' % current_thread().name) t1 = Thread(target=eat1) t2 = Thread(target=eat2) t1.start() t2.start()
9.递归锁
Rlock 称之为递归锁或者可重入锁
Rlock不是用来解决死锁问题的
与Lock唯一的区别: Rlock同一线程可以多次执行acquire 但是执行几次acquire就应该对应release几次
from threading import RLock,Lock,Thread r = RLock() def task(): r.acquire() print('子进程开始') r.release() # 主线程锁了一次 r.acquire() r.acquire() # 有几次acquire,就需要对应几次release r.release() r.release() t1 = Thread(target=task) t1.start()
10.信号量
''' 信号量 Lock Rlock 可以限制被锁定的代码,同时可以被多少线程并发访问 Lock 锁住一个马桶,同时只能有一个人 Semaphore 锁住的是公共厕所,同时可以来一堆人 用途:仅用于控制并发访问,并不能防止并发修改造成的问题 ''' from threading import Semaphore,Thread import time # 设置每次访问的线程数量,它自带锁 s = Semaphore(5) def task(): # 使用锁把代码锁住,让其他进程暂时无法访问资源 s.acquire() print('子run') time.sleep(2) print('子over') s.release() for i in range(10): t = Thread(target=task) t.start()
总结:
多线程
线程是CPU的最小执行单位, 线程本质是一堆代码构成的执行流程
线程被包含在进程中,
进程是一个资源单位,其中存储着该程序运行所需所有资源, 可以比喻为一个车间
线程就是车间中国一条流水线,上面放的是制作产品的具体方法(就是的代码)
一个进程至少有一个线程,操作系统在运行一个程序时 会在进程中自动开启一条线程
一个进程中可以有多个线程
同一个进程中的线程共享进程内的数据
创建线程速度非常快 开销小
线程之间没有父子关系 都是平等的
使用方式和进程一致
学习多线程 多进程 都是为了提高效率 ,通过并发执行多个任务