- 安全性
- 高并发
一 扫盲
任务调度
1. 大部分操作系统的任务调度是采用时间片轮转的抢占式调用
时间片:某一小段时间---操作系统
任务执行的一小段时间
是由操作系统进行分配的(每次的时间片不一定相等)
程序执行的状态:运行状态
程序不执行的状态:
1. 结束:终止状态
2. 不结束:阻塞状态(input())
2. 任务执行一段时间后,强制暂停,执行下一个任务
3. cpu的计算速度很快,上下文切换较快,时间片很短,人无法感知,人误以为是同时执行的多个任务
4. 宏观并行,微观串行
从宏观角度:人认为是同时执行多个任务
从微观角度:任何时刻只有一个任务在执行(仅限于单核)
5. 在多核模式下可以达到真正的并行
二 进程和线程的区别
进程:
计算机的核心:cpu ---计算
计算机的管理者:操作系统---负责任务调度,资源分配,资源的管理,统计整个计算机硬件
所有的程序运行在操作系统中的
1. 进程是一个具有一定独立功能的程序,是在一个数据集合上的一次动态执行过程,是操作系统进行资源分配的一个 独立单位,是应用程序运行的载体
2. 进程是一种抽象的概念,从来没有一个标准定义,进程的结构:
1. 程序:用于描述进程要完成的功能,是控制进程的指令集
2. 数据集合:是程序在执行时所需要的数据和工作区
3. 程序控制块:包含进程的描述信息和控制信息,是进程存在的唯一标志
3. 进程的特点:
1. 动态性
2. 并发性
3. 独立性
4. 结构性
4. 进程和进程之间资源相互独立,互不干扰
线程:
是轻量级的进程(进程中包含线程)
1. 进程太重,上下文切换消耗太大,出现了线程的概念,线程的调度方式同进程
时间片轮转抢占式调度
线程资源分配的最小单位
线程和线程之间资源共享(共享进程的资源)
2. 线程的结构:
1. 线程ID
2. 指令指针
3. 寄存器
4. 堆栈
3. 线程的上下文切换,远快于进程上下文的切换
4. 一个进程是由多个线程组成,线程是一个进程中代码的不同执行路线
单线程和多线程
1. 单线程:在一个进程中,只有一个线程,程序的所有资源只提供给一个线程使用
2. 多线程:在一个进程中有多个线程,多个线程之间会并发执行(宏观并行,微观串行),相互抢占进程的资源
三 多线程模块:threading
Python: CPython
GIL:全局解释锁:保证每次只有一个线程可以执行
GIL导致CPython中的多核模式下的多线程几乎等同于单线程,单核几乎不影响
只有CPython有GIL
一般情况:多个线程争抢进程中的资源
CPython中:多个线程争抢GIL资源
threading模块:是python并发库的模块
Python2: thead threading p
ython3: threading
创建线程的两种方式:
1. 继承Thread类,并覆盖run方法
2. 直接使用Thread的构造方法
Thread.__init__(self, group=None, target=None, name=None,args=(), kwargs=None, daemon=None)
self:当前对象
group:预留参数
target:目标(要做的事--函数对象)
name:设置线程的名字
args:必须是元组,是target的参数
kwargs:必须是字典,是target的参数
daemon:设置守护线程
3. 任何程序都至少有一个线程:主线程
注意:Thread类是threading模块中的一个类
threading.Thread
# 单线程
def music(name,count):
for i in range(count):
print('list to music%s %d times'%(name,i+1))
def movie(name,count):
for i in range(count):
print('list to movie %s %d times'%(name,i+1))
if __name__=='__main__':
music('凉凉',3)
movie('我不是药神',3)
# 多线程
class Music(threading.Thread): # 继承Thread类
def __init__(self,name,count):
super().__init__()
self.name=name
self.count=count
def run(self): # 覆盖run方法
for i in range(self.count):
print('list to music%s %d times' % (self.name, i + 1))
time.sleep(0.5)
#####################################################
def movieFunc(name,count):
for i in range(count):
print('list to movie %s %d times'%(name,i+1))
time.sleep(0.5)
if __name__ == '__main__':
music=Music('凉凉',3) #创建线程对象
movie=threading.Thread(target=movieFunc,args=('我不是药神',3)) # target:目标,要执行 的内容
music.start() #启动线程
movie.start()
四 Thread类中的其他方法
1. getName()/name:
获得线程的名字
默认是Thread-N
2. setName(newName)/直接在构造方法中修改:
设置线程名称
3. ident:
线程的id
线程id只有在start之后才会被分配
4. is_alive()/isAlive:
判断线程是否存活(激活状态)
5. join([timeout]):
timeout:设置超时时间
会阻塞调用该方法的线程(当前线程),执行被调用的线程,直到被调用的线程执行完毕或到达超时时长 时,才继续执行当前线程
如果timeout不设置:不限制超时时间
6. setDaemon(bool):
设置该线程为守护线程(精灵线程)
主线程中的子线程如果设置为守护线程,当主线程结束时,会杀死所有守护线程(不论守护线程是否结束)
典型的守护线程:GC—-垃圾回收机制
主线程和子线程
主线程:不通过Thread创建的线程
子线程:通过Thread创建的线程
import threading,time
def music(name,n):
for i in range(n):
print('listen to the music %s %s times'%(name,i+1))
time.sleep(0.5)
def movie(name,n):
for i in range(n):
print('watch the movie %s %s times'%(name,i+1))
time.sleep(1)
if __name__ == '__main__':
t1=threading.Thread(target=music,args=('凉凉',3),daemon=False)
t2=threading.Thread(target=movie,kwargs={'name':'我不是药神','n':3},name='hehe3')
t1.setDaemon(True)
t2.setDaemon(True)
# t1.setName('hehe')
# t1.name='hehe2'
# print(t1.getName(),t2.getName())
# print(t1.name,t2.name)
# print(t1.is_alive())
t1.start()
t2.start()
# print(t1.ident, t2.ident)
# time.sleep(4)
# print(t1.is_alive())
# print(t1.isAlive)
# t1.join()
# t2.join()
# print('end')
print('end')
五 线程的状态
Python 有5个状态
六 GIL锁
GIL:Global Interpreter Lock
1. GIL并不是Python的特性,Python完全可以不用GIL CPython使用GIL Jython,PyPy,IronPython
2. 对于多核,GIL锁将多核性质几乎限制为单核性质
3. 多进程和多线程使用的时机:
1. CPU密集型程序宜用多进程
2. I/O密集型程序宜用多线程
4. CPython中GIL锁的弊端:
1. GIL锁管理的线程之间,挣钱的不是时间片,而是锁标记
2. 在单核模式下,和没有GIL锁的程序没有太大区别
3. GIL锁标记的从释放到再获取的时间间隔极短,导致在多核模式下,其他核被激活但无法获得锁标记,导 致CPU使用效率大打折扣
5. GIL优化:
1. GIL短时间内不可能取消
2. 延长GIL所标记从释放到再获取的时间(最后手段)
3. 增加其他线程的权重
4. 控制每个线程执行数据集合的总量(字节)
七 线程同步
同步:数据安全
原子操作:从业务角度,是不可分割的的操作
一个流程中,不可被分割的整体
临界资源:任何线程都可以访问的资源
同步:保证原子操作不被破坏的操作
原子操作如果被破坏则会导致数据读取不一致(脏读)
方案:加锁
加锁:同步锁,线程锁,互斥锁,排他锁
效率降低,换来数据的安全
import time,threading
class MyList(list):
def __init__(self):
self.l=['A','B','','','','','']
self.index=2
def add(self,value):
self.l[self.index]=value
time.sleep(0.0001)
self.index+=1
def getList(self):
return self.l
m=MyList()
def fun(char):
m.add(char)
if __name__ == '__main__':
t1=threading.Thread(target=fun,args=('C',))
t2=threading.Thread(target=fun,args=('D',))
t1.start()
t2.start()
t1.join()
t2.join()
print(m.getList())
# 加锁
import time,threading
lock=threading.Lock() # 创建锁对象
class MyList(list):
def __init__(self):
self.l=['A','B','','','','','']
self.index=2
def add(self,value):
lock.acquire() # 获取所标记
self.l[self.index]=value
time.sleep(3)
self.index+=1
lock.release() # 释放所标记
def getList(self):
return self.l
m=MyList()
def fun(char):
m.add(char)
if __name__ == '__main__':
t1=threading.Thread(target=fun,args=('C',))
t2=threading.Thread(target=fun,args=('D',))
t1.start()
t2.start()
t1.join()
t2.join()
print(m.getList())
八 死锁问题
如果一个原子操作,被重复加锁,会导致死锁问题
解决方案:
RLock类:可重入锁
可以重复的对资源进行加锁和释放
九 补充ThreadLocal
1. 全局变量不安全,可以使用局部变量
2. 局部变量占用空间,使用麻烦,作用域小
3. 全局变量,为了安全,可以加锁,加锁又会导致效率低
4. 刚需:既有全局的使用便利性,又有局部的安全性,且效率高的一种变量
ThreadLocal:线程绑定对象
注意: ThreadLocal对象是并发库中的高级对象
同时拥有全局变量和局部变量的性质
import threading,time
l=threading.local()
def fun1():
l.a=20
time.sleep(1)
print(l.a)
def fun2():
l.a=30
print(l.a)
if __name__ == '__main__':
t1=threading.Thread(target=fun1)
t2=threading.Thread(target=fun2)
t1.start()
t2.start()
t1.join()
t2.join()