a.进程
我们电脑的应用程序,都是进程,假设我们用的电脑是单核的,cpu同时只能执行一个进程。当程序处于I/O阻塞的时候,CPU如果和程序一起等待,那就太浪费了,cpu会去执行其他的程序,此时就涉及到切换,切换前要保存上一个程序运行的状态,才能恢复,所以就需要有个东西来记录这个东西,就可以引出进程的概念了。 进程就是一个程序在一个数据集上的一次动态执行过程。进程由程序,数据集,进程控制块三部分组成。程序用来描述进程哪些功能以及如何完成;数据集是程序执行过程中所使用的资源;进程控制块用来保存程序运行的状态
b.线程
线程又叫轻量级进程,是一个基本的cpu执行单元,也是程序执行过程中的最小单元。一个进程最少也会有一个主线程,在主线程中通过threading模块,在开子线程
c.进程和线程的关系
(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程 (2)资源分配给进程,进程是程序的主体,同一进程的所有线程共享该进程的所有资源 (3)cpu分配给线程,即真正在cpu上运行的是线程 (4)线程是最小的执行单元,进程是最小的资源管理单元
d.并行和并发
并行处理是指计算机系统中能同时执行两个或多个任务的计算方法,并行处理可同时工作于同一程序的不同方面 并发处理是同一时间段内有几个程序都在一个cpu中处于运行状态,但任一时刻只有一个程序在cpu上运行。 并发的重点在于有处理多个任务的能力,不一定要同时;而并行的重点在于就是有同时处理多个任务的能力。并行是并发的子集
以上所说的是相对于所有语言来说的,Python的特殊之处在于Python有一把GIL锁,这把锁限制了同一时间内一个进程只能有一个线程能使用cpu
python 中,有关线程开发的部分被单独封装到了模块中,和线程相关的模块有以下 2 个: _thread:是 Python 3 以前版本中 thread 模块的重命名,此模块仅提供了低级别的、原始的线程支持,以及一个简单的锁。功能比较有限。正如它的名字所暗示的(以 _ 开头),一般不建议使用 thread 模块; threading:Python 3 之后的线程模块,提供了功能丰富的多线程支持,推荐使用。
import threading import time ''' # 创建线程的第一种方式:直接创建线程 (常用) # 定义线程执行的函数 def fn(n): print("=========%s"%n) time.sleep(3) print("thread-1") # 创建线程 args接收线程函数中的参数,参数格式是一个元组 t1是创建的子线程对象 t1 = threading.Thread(target=fn,args=('hehe',)) t1.start() # 启动子线程 print() print("end......") # 主线程 ''' # 第二种创建线程的方式:通过继承创建线程 class MyThread(threading.Thread): def __init__(self): threading.Thread.__init__(self) # 重写threading.Thread中的run()方法 def run(self): print("自定义线程的逻辑") time.sleep(3) print("ednding.......") # 创建线程对象 t2 = MyThread() t2.start() # 子线程 print() print("lalala") # 主线程
在介绍Python中的线程之前,先明确一个问题,Python中的多线程是假的多线程! 为什么这么说,我们先明确一个概念,全局解释器锁(GIL)
3.1什么是GIL
Python代码的执行由Python虚拟机(解释器)来控制,同时只有一个线程在执行。对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同时只有一个线程在运行。
3.2为什么要GIL
为了线程间数据的一致性和状态同步的完整性
3.3GIL的影响
只有一个线程在运行,无法使用多核(多个CPU)。 在多线程环境中,Python虚拟机按照以下方式执行。 1.设置GIL。 2.切换到一个线程去执行。 3.运行。 4.把线程设置为睡眠状态。 5.解锁GIL。 6.再次重复以上步骤。 比方我有一个4核的CPU,那么这样一来,在单位时间内每个核只能跑一个线程,然后时间片轮转切换。 但是Python不一样,它不管你有几个核,单位时间多个核只能跑一个线程,然后时间片轮转。 执行一段时间后让出,多线程在Python中只能交替执行,10核也只能用到1个核
3.4多线程怎么使用多核
1、重写python编译器(官方cpython)如使用:PyPy解释器 2、调用C语言的链接库 3. 不用线程,改用进程
3.5cpu密集型(计算密集型)、I/O密集型
计算密集型就是计算、逻辑判断量非常大而且集中的类型,因为主要占用cpu资源所以又叫cpu密集型,而且当计算任务数等于cpu核心数的时候,是cpu运行效率最高的时候。 计算密集型任务由于主要消耗CPU资源,代码运行效率至关重要,C语言编写 特点:消耗cpu 比如云计算 建议使用多进程 IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成,99%的时间花费在IO上,脚本语言是首选,C语言最差。 IO密集型: Input output(一般是比较耗时的操作), IO密集型就是磁盘的读取数据和输出数据非常大的时候就是属于IO密集型 比如网络传输、数据库等调用、爬虫等 建议使用多线程
3.6多线程类似于同时执行多个不同程序,多线程运行有如下优点:
1.使用线程可以把占据长时间的程序中的任务放到后台去处理。 2.用户界面可以更加吸引人,比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度。 3.程序的运行速度可能加快。 4.在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。
并行 并发 阻塞 非阻塞
阻塞:是指等调用结果返回之前,调用者都会进入阻塞状态等待,只有在等到结果之后再返回. 非阻塞:是指不能立即得到结果之前,该函数不会阻塞当前线程,而会立刻返回. 并发:是指在一个时间段内,有几个程序都在 同一个cpu 上运行,但是在任意时刻点上只有一个程序在处理机上运行. 并行:是指在一个时间段内,有几个程序都在 几个cpu 上运行,任意时刻点上,有多个程序同时运行,并且多道程序之间互不干扰.
比如要吃水饺 单线程: 择菜 剁馅 和面 擀皮 包饺子 烧水 煮水饺 (同步) 多线程(异步): a 择菜 剁馅 b 和面 擀皮 包饺子 c 烧水 煮水饺
python全局GIL解释器锁演示 ''' from threading import Thread # 使用线程 def loop(): while True: print("线程演示") if __name__ == "__main__": # 创建了3个线程 for i in range(3): t = Thread(target=loop) t.start() ''' # 多进程 from multiprocessing import Process def loop(): while True: print("进程演示") if __name__ == "__main__": for i in range(3): t = Process(target=loop) t.start()
# 多线程:是多个线程并行的技术 import threading import time import random ''' # 第一种: 最原始的创建多线程的方式 # 定义一个线程函数 def fn(*args): time.sleep(random.uniform(1,4)) print("子线程:",args) if __name__ == "__main__": # 原始的方法创建多个线程 t1 = threading.Thread(target=fn,args=("叶子楣",)) t1.start() t1.join() # 暂停阻塞 等待t1执行完后,才会跳过(不会影响t2和t3的线程) t2 = threading.Thread(target=fn,args=("王祖贤",)) t2.start() t2.join() # 暂停阻塞 等待t2执行完后,才会跳过(不会影响t1和t3的线程) t3 = threading.Thread(target=fn,args=("翁虹",)) t3.start() t3.join() # 暂停阻塞 等待t3执行完后,才会跳过(不会影响t2和t1的线程) ''' # 第二种:简便的方式创建多线程 # 定义一个线程函数 def fn(*args): time.sleep(random.uniform(1,4)) print("子线程:",args) if __name__ == "__main__": # 定义一个空列表 接收创建的所有的子线程 t_list = [] for i in range(1,4): # 创建一个线程 t = threading.Thread(target=fn) t.start() # 将创建的子线程追加到线程列表中去 t_list.append(t) # 其他的属性和方法:(了解) #print(t.name,t.getName()) # 获取当前线程的名称 #print(t.daemon) # 守护线程 默认值是False 值为False表示该线程为非守护线程 # print(t.ident) # 线程号 # print(t.isAlive()) # 查看线程是否在执行 # print(threading.enumerate()) # 列举所有在运行的线程 print(threading.active_count()) # 统计正在运行的线程的数量 # 等到子线程执行完毕后,再执行下面的程序 for t in t_list: t.join()
4.1线程冲突
# 在没有使用多线程之前 ''' num = 0 def add(): global num for i in range(100000): num += 1 print(num) for i in range(5): add() # 使用多线程 出现了线程冲突的现象,多个线程之间会去争抢一个资源(num) import threading num = 0 def add1(): global num for i in range(1000000): num += 1 print(num) def create_thread(): for i in range(5): threading.Thread(target=add1).start() if __name__ == "__main__": create_thread() ''' # 如何解决线程冲突呢? 通过线程锁解决 import threading # 定义线程锁 lock = threading.Lock() ''' # 第一种:手动添加线程锁和解开线程锁 num = 0 def add2(): # 添加线程锁 lock.acquire() global num for i in range(1000000): num += 1 print(num) # 解开线程锁 lock.release() def create_thread1(): for i in range(5): threading.Thread(target=add2).start() if __name__=="__main__": create_thread1() # 第一种:手动添加线程锁和解开线程锁 ''' # 第二种:自动添加线程锁和解开线程锁 使用with(常用) num = 0 def add2(): # 自动添加线程锁和解开线程锁 with lock: global num for i in range(1000000): num += 1 print(num) def create_thread1(): for i in range(5): threading.Thread(target=add2).start() if __name__=="__main__": create_thread1()
4.2死锁
死锁:是指一个资源被多次调用,而多次调用方都未能释放该资源就会造成一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。 互相锁住对方线程需要的资源,造成死锁局面 Thread1 Thread2 A(属于Thread1) B(属于Thread2) B A
4.3线程安全
递归锁
递归锁,重用锁,用于解决死锁的问题,可重复锁 # 创建递归锁 rlock = threading.RLOCK()
4.4信号量Semaphore调度线程:控制最大并发量
https://www.cnblogs.com/aylin/p/5601969.html 博客地址
https://blog.csdn.net/sinat_38682860/article/details/80761528