您现在的位置是:网站首页> 编程资料编程资料
详解Python中的多线程_python_
2023-05-26
345人已围观
简介 详解Python中的多线程_python_
什么是多线程:
进程:正在运行的程序,QQ 360 ......
线程:就是进程中一条执行程序的执行路径,一个程序至少有一条执行路径。(360中的杀毒 电脑体检 电脑清理 同时运行的话就需要开启多条路径)
每个线程都有自己需要运行的内容,而这些内容可以称为线程要执行的任务。
开启多线程是为了同时运行多部分代码。
好处:解决了多部分需要同时运行的问题
弊端:如果线程过多,会导致效率很低(因为程序的执行都是CPU做着随机 快速切换来完成的)
- 线程与进程的区别
- 线程共享内存,进程独立内存
- 线程启动速度块,进程启动速度慢,运行时速度没有可比性
- 同一个进程的线程间可以直接交流,两个进程想通信,必须通过一个中间代理来实现
- 创建新线程很简单,创建新进程需要对其父进程进行一次克隆
- 一个线程可以控制和操作同一线程里的其他线程,但是进程只能操作子进程
threading模块
多线程的使用方式一:直接使用
# -*- coding:utf-8 -*- # 线程使用的方式一 import threading import time # 需要多线程运行的函数 def fun(args): print("我是线程%s" % args) time.sleep(2) print("线程%s运行结束" % args) # 创建线程 t1 = threading.Thread(target=fun, args=(1,)) t2 = threading.Thread(target=fun, args=(2,)) start_time = time.time() t1.start() t2.start() end_time = time.time() print("两个线程一共的运行时间为:", end_time-start_time) print("主线程结束") """ 运行结果: 我是线程1 我是线程2两个线程一共的运行时间为: 0.0010077953338623047 主线程结束 线程1运行结束 线程2运行结束 """线程的第二种使用方式:继承式调用
# 继承式调用 import threading import time class MyThreading(threading.Thread): def __init__(self, name): super(MyThreading, self).__init__() self.name = name # 线程要运行的代码 def run(self): print("我是线程%s" % self.name) time.sleep(2) print("线程%s运行结束" % self.name) t1 = MyThreading(1) t2 = MyThreading(2) start_time = time.time() t1.start() t2.start() end_time = time.time() print("两个线程一共的运行时间为:", end_time-start_time) print("主线程结束") """ 运行结果: 我是线程1 我是线程2 两个线程一共的运行时间为: 0.0010724067687988281 主线程结束 线程2运行结束 线程1运行结束 """守护线程与join方法
- 在Python多线程编程中,join方法的作用式线程同步。
- 守护线程,是为守护别人而存在的,当设置为守护线程后,被守护的主线程不存在后,守护线程也自然不存在。
- 第一种:python多线程默认情况
- Python多线程默认情况(设置线程setDaemon(False)),主线程执行完自己的任务后,就退出了,此时子线程会继续执行自己的任务,直到子线程任务结束
- 代码演示:threading中的两个创建多线成的例子都是。
- 第二种:开启守护线程
- 开启线程的setDaemon(True)),设置子线程为守护线程,实现主程序结束,子程序立马全部结束功能
- 代码演示:
# 守护线程 import threading import time class MyThreading(threading.Thread): def __init__(self, name): super(MyThreading, self).__init__() self.name = name # 线程要运行的代码 def run(self): print("我是线程%s" % self.name) time.sleep(2) print("线程%s运行结束" % self.name) t1 = MyThreading(1) t2 = MyThreading(2) start_time = time.time() t1.setDaemon(True) t1.start() t2.setDaemon(True) t2.start() end_time = time.time() print("两个线程一共的运行时间为:", end_time-start_time) print("主线程结束")- 注意:如果要设置为守护线程,一定要在开启线程之前,将该线程设置为守护线程
- 结论:主线程结束后,无论子线程1,2是否运行完成,都结束线程,不在继续向下运行
- 第三种:加入join方法设置同步
- 当不给程序设置守护进程时,主程序将一直等待子程序全部运行完成才结束
- 代码演示:
# join:线程同步 import threading import time class MyThreading(threading.Thread): def __init__(self, name): super(MyThreading, self).__init__() self.name = name # 线程要运行的代码 def run(self): print("我是线程%s" % self.name) time.sleep(3) print("线程%s运行结束" % self.name) threading_list = [] start_time = time.time() for x in range(50): t = MyThreading(x) t.start() threading_list.append(t) for x in threading_list: x.join() # 为线程开启同步 end_time = time.time() print("50个线程一共的运行时间为:", end_time-start_time) print("主线程结束")结论:主线程等待50个子线程全部执行完成才结束。
线程锁(互斥锁Mutex)
- 一个进程下可以启用多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时如果多个线程同时要修改一份数据,会出现什么状况?
- 代码演示:
# -*- coding:utf8 -*- import threading import time num = 100 threading_list = [] def fun(): global num print("get num:", num) num += 1 time.sleep(1) for x in range(200): t = threading.Thread(target=fun) t.start() threading_list.append(t) for x in threading_list: x.join() print("nun:", num)- 结论:运行结果可能会出现num<300的情况
- 正常来讲,这个num结果应该是300, 但在python 2.7上多运行几次,会发现,最后打印出来的num结果不总是300,为什么每次运行的结果不一样呢? 哈,很简单,假设你有A,B两个线程,此时都 要对num 进行加1操作, 由于2个线程是并发同时运行的,所以2个线程很有可能同时拿走了num=100这个初始变量交给cpu去运算,当A线程去处完的结果是101,但此时B线程运算完的结果也是101,两个线程同时CPU运算的结果再赋值给num变量后,结果就都是101。那怎么办呢? 很简单,每个线程在要修改公共数据时,为了避免自己在还没改完的时候别人也来修改此数据,可以给这个数据加一把锁, 这样其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。
*注:不要在3.x上运行,不知为什么,3.x上的结果总是正确的,可能是自动加了锁
加锁版本:
import random import threading import time num = 100 threading_list = [] def fun(): global num time.sleep(random.random()) lock.acquire() # 加锁 print("get num:", num, threading.current_thread()) num += 1 lock.release() # 释放锁 # 实例化锁对象 lock = threading.Lock() for x in range(200): t = threading.Thread(target=fun) t.start() threading_list.append(t) for x in threading_list: x.join() print("num:", num)GIL VS Lock
机智的同学可能会问到这个问题,就是既然你之前说过了,Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock? 注意啦,这里的lock是用户级的lock,跟那个GIL没关系 ,具体我们通过下图来看一下+配合我现场讲给大家,就明白了。

那你又问了, 既然用户程序已经自己有锁了,那为什么C python还需要GIL呢?加入GIL主要的原因是为了降低程序的开发的复杂度,比如现在的你写python不需要关心内存回收的问题,因为Python解释器帮你自动定期进行内存回收,你可以理解为python解释器里有一个独立的线程,每过一段时间它起wake up做一次全局轮询看看哪些内存数据是可以被清空的,此时你自己的程序 里的线程和 py解释器自己的线程是并发运行的,假设你的线程删除了一个变量,py解释器的垃圾回收线程在清空这个变量的过程中的clearing时刻,可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了,结果就有可能新赋值的数据被删除了,为了解决类似的问题,python解释器简单粗暴的加了锁,即当一个线程运行时,其它人都不能动,这样就解决了上述的问题, 这可以说是Python早期版本的遗留问题。
RLock(递归锁)
说白了就是在一个大锁中还要再包含子锁
import threading, time def run1(): lock.acquire() print("grab the first part data") global num num += 1 lock.release() return num def run2(): lock.acquire() print("grab the second part data") global num2 num2 += 1 lock.release() return num2 def run3(): lock.acquire() res = run1() print('--------between run1 and run2-----') res2 = run2() lock.release() print(res, res2) if __name__ == '__main__': num, num2 = 0, 0 lock = threading.RLock() for i in range(3): t = threading.Thread(target=run3) t.start() while threading.active_count() != 1: print(threading.active_count()) else: print('----all threads done---') print(num, num2)在开发的过程中要注意有些操作默认都是 线程安全的(内部集成了锁的机制),我们在使用的时无需再通过锁再处理,例如:
import threading data_list = [] lock_object = threading.RLock() def task(): print("开始") for i in range(1000000): data_list.append(i) print(len(data_list)) for i in range(2): t = threading.Thread(target=task) t.start()
Semaphore(信号量)
- 互斥锁同时只允许一个线程修改数据,而Semaphore是同时允许一定数量的线程修改数据,比如厕所有三个坑,那最多只允许三个人上厕所,后面的人只能等前面的人出来才能进去。
- 代码演示:
# -*- coding:GBK -*- import threading import time sum_1 = 0 def run(i): global sum_1 time.sleep(1) # lock.acquire() semaphore.acquire() sum_1 += 1 print("线程%s来了,并修改了sum_1的值为:%s" % (i, sum_1)) semaphore.release() # lock.release() # lock = threading.Lock() semaphore = threading.BoundedSemaphore(5) for x in range(10): t = threading.Thread(target=run, args=(x,)) t.start() while threading.active_count() != 1: pass print("程序结束")Event(事件)
- 通过Event来实现两个或多个线程间的交互,下面是一个红绿灯的例子,即起动一个线程做交通指挥灯,生成几个线程做车辆,车辆行驶按红灯停,绿灯行的规则。
- 四个常用方法
set() # 设置标志位为 True
clear() # 清空标志位(将标志位改为false)
is_set() # 检测标志位,如果标志位被设置,返回True,否则返回False
wait() # 等待标志位被设置位True程序才继续往下运行
代码演示:
# -*- coding:utf-8 -*- import threading import time def light(): count = 1 event.set() # 设置标志位 True while True: if count <= 10: print("现在是绿灯") time.sleep(1) elif count <= 15: print("现在是红灯") event.clear() # 清空标志位(将标志位改为false) time.sleep(1) else: count = 0 event.set() count += 1 def car(name): while True: if event.is_set(): print("----------%s在起飞-------------" % name) time.sleep(1) else: print("---------%s在等红灯---------------" % name) event.wait() # 等待标志位被设置位True程序才继续往下运行 event = threading.Event() light_1 = threading.Thread(target=light) light_1.start() for x in range(5): car_1 = threading.Thread(target=car, args=("马自达"+str(x),)) car_1.start()红绿灯案例
Queue(队列)
queue.Queue(maxsize=0) #队列:先进先出 maxsize:设置队列的大小 queue.LifoQueue(maxsize=0) ##last in fisrt out maxsize:设置队列的大小 queue.PriorityQueue(maxsize=0) #存储数据时可设置优先级的队列,按优先级顺序(最低的先) maxsize:设置队列的大小
exceptionqueue.Empty
Exception raised when non-blocking get()(or get_nowait()) is called on a Queue object which is empty.
当在一个空的队列对象上调用非阻塞的get()(或get_nowait())时,会产生异常。
exceptionqueue.Full
Exception raised when non-blocking put() (or 提示:
本文由神整理自网络,如有侵权请联系本站删除!
本站声明:
1、本站所有资源均来源于互联网,不保证100%完整、不提供任何技术支持;
2、本站所发布的文章以及附件仅限用于学习和研究目的;不得将用于商业或者非法用途;否则由此产生的法律后果,本站概不负责!
