进程线程的特点
-
每个进程都有自己 独立的 内存地址
-
线程之间的切换 相对于 进程之间切换 更为方便,代价也更低。
-
对于IO密集型的任务,使用多线程还是能提高一下CPU使用率。
-
对于CPU密集型的任务,Python中的多线程其实是个鸡肋……没卵用……在Python的解释器CPython中存在一个互斥锁。简单来讲就是同一时间只能有一个线程在执行,其它线程都处于block模式。
-
要想在py中充分利用多核cpu,就只能用多进程了。虽然代价高了些,但是比起并行计算带来的性能提升这些也微不足道了。
-
进程与线程的关系区别 一个程序至少有一个进程,一个进程至少有一个线程.(进程可以理解成线程的容器)进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈)但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源. 一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行. 进程:本质上就是一段程序的运行过程 线程:最小的执行单元(实例) 进程:最小的资源单位 程序计数器: 1.寄存器:cpu与内存中间的一级二级三级缓存,缓存存的是进程线程之间的切换时要保存状态的变量等关键数据、2.栈 一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行
线程
什么是线程 进程线程其实都是虚拟单位,都是用来帮助我们形象的描述某种事物
进程:资源单位
线程:执行单位
将内存比如成工厂
那么进程就相当于是工厂里面的车间
而你的线程就相当于是车间里面的流水线
ps:每个进程都自带一个线程,线程才是真正的执行单位,进程只是在线程运行过程中
提供代码运行所需要的资源
为什么要有线程 开进程 1.申请内存空间 耗资源 2."拷贝代码" 耗资源
开线程
一个进程内可以起多个线程,并且线程与线程之间数据是共享的
ps:开启线程的开销要远远小于开启进程的开销
线程的定义 线程的出现是为了降低上下文切换的消耗,提高系统的并发性,并突破一个进程只能干一样事的缺陷,使到进程内并发成为可能。
线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合和堆栈共同组成。线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能。线程没有自己的系统资源。
开启线程 方式1:
from threading import Thread
import time
def task(name):
print('%s is running'%name)
time.sleep(3)
print('%s is over'%name)
# 开线程不需要在__main__代码块内 但是习惯性的还是写在__main__代码块内
t = Thread(target=task,args=('egon',))
t.start() # 告诉操作系统开辟一个线程 线程的开销远远小于进程
# 小的代码执行完 线程就已经开启了
print('主')
方式2:继承
import threading
import time
class MyThread(threading.Thread):
def __init__(self, num):
threading.Thread.__init__(self)
self.num = num
def run(self): # 定义每个线程要运行的函数
#这个方法名字不能改
print("running on number:%s" % self.num)
time.sleep(3)
if __name__ == '__main__':
t1 = MyThread(1)
t2 = MyThread(2)
t1.start()
t2.start()
print("ending......")
线程方法 join():在子线程完成运行之前,这个子线程的父线程将一直被阻塞。 setDaemon(True):将线程声明为守护线程,必须在start() 方法调用之前设置,我们需要的是只要主线程完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以 用setDaemon方法
Thread实例的方法
# run(): 线程被cpu调度后自动执行线程对象的run方法
# start():启动线程活动。
# isAlive(): 返回线程是否活动的。
# getName(): 返回线程名。
# setName(): 设置线程名。
threading模块提供的一些方法:
# threading.currentThread(): 返回当前的线程变量。
# threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
# threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
from threading import Thread,current_thread,active_count
import time
import os
def task(name,i):
print('%s is running'%name)
# print('子current_thread:',current_thread().name)
# print('子',os.getpid())
time.sleep(i)
print('%s is over'%name)
# 开线程不需要在__main__代码块内 但是习惯性的还是写在__main__代码块内
t = Thread(target=task,args=('egon',1))
t1 = Thread(target=task,args=('jason',2))
t.start() # 告诉操作系统开辟一个线程 线程的开销远远小于进程
t1.start() # 告诉操作系统开辟一个线程 线程的开销远远小于进程
t1.join() # 主线程等待子线程运行完毕
print('当前正在活跃的线程数',active_count())
# 小的代码执行完 线程就已经开启了
print('主')
# print('主current_thread:',current_thread().name)
# print('主',os.getpid())
主线程运行结束之后需要等待子线程结束才能结束呢? """ 主线程的结束也就意味着进程的结束 主线程必须等待其他非守护线程的结束才能结束 (意味子线程在运行的时候需要使用进程中的资源,而主线程一旦结束了资源也就销毁了) """ 守护线程
from threading import Thread,current_thread
import time
def task(i):
print(current_thread().name)
time.sleep(i)
print('GG')
# for i in range(3):
# t = Thread(target=task,args=(i,))
# t.start()
t = Thread(target=task,args=(1,))
t.daemon = True
t.start()
print('主')
线程间通信 1.定义变量 2.函数中,对变量进行改动时,在变量前加global(全局操作变量)
线程的互斥锁
from threading import Thread,Lock
import time
n = 100
def task(mutex):
global n
mutex.acquire()
tmp = n
time.sleep(0.1)
n = tmp - 1
mutex.release()
t_list = []
mutex = Lock()
for i in range(100):
t = Thread(target=task,args=(mutex,))
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(n)
并发并行、同步异步、阻塞非阻塞
-
并发 & 并行
并发:指系统具有处理多个任务(动作)的能力 并行:指系统具有 同一时刻 处理多个任务(动作)的能力,是 并发 的一种
-
同步&异步
同步:当进程执行到一个IO操作时候(等待外部数据)--等待不能干别的:同步 异步:当进程执行到一个IO操作时候(等待外部数据)--不等待可干别的:可干别的直到数据接受成功,再回来处理
-
阻塞非阻塞
阻塞: 阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。函数只有在得到结果之后才会返回。 非阻塞:非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
CPU计算任务切换条件:遇到IO堵塞 或 时间轮训
问题:多核没用上 GIL:全局解释锁 在cpython解释器上 同一时刻,只有一个线程被CPU执行
任务:IO密集型 计算密集型 对于IO密集型任务,Python的多线程是有意义的;对计算密集型的任务,Python的多线程是没意义的 解决办法:多进程+协程(进程消耗大,通信麻烦,却是利用多核的唯一方法) 对于计算密集型任务,python就不适用了
-
同步锁 等的时间太长了,每一个线程拿到的值都是初始值,导致真实的函数只执行了一次 多个线程都在同时操作同一个共享资源,所以造成了资源破坏,怎么办呢?(join会造成串行,失去多线程的意义)我们可以通过同步锁来解决这种问题
import threading
import time
def sub():
global num
# num-=1
temp=num
time.sleep(1)
num=temp-1
num=100
l=[]
for i in range(100):
t=threading.Thread(target=sub)
t.start()
l.append(t)
for t in l:
t.join()
print(num)
##################
答案会因 time.sleep(1) 时间长短的不同而不同
解决后
import threading
import time
def sub():
global num
# num-=1
print ("ok")
lock.acquire()
temp=num
time.sleep(1)
num=temp-1
lock.release()
num=100
l=[]
lock=threading.Lock()
for i in range(100):
t=threading.Thread(target=sub)
t.start()
l.append(t)
for t in l:
t.join()
print(num)
-
死锁 递归锁 死锁 在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去。下面是一个死锁的例子:
import threading
import time
class MyThread(threading.Thread):
def actionA(self):
A.acquire()
print(self.name,"gotA",time.ctime())
time.sleep(2)
B.acquire()
print(self.name, "gotB", time.ctime())
time.sleep(1)
B.release()
A.release()
def actionB(self):
B.acquire()
print(self.name, "gotB", time.ctime())
time.sleep(2)
A.acquire()
print(self.name, "gotA", time.ctime())
time.sleep(1)
A.release()
B.release()
def run(self):
self.actionA()
self.actionB()
if __name__ == '__main__':
A=threading.Lock()
B=threading.Lock()
#r_lcok=threading.RLock()
L=[]
for i in range(5):
t=MyThread()
t.start()
L.append(t)
for i in L:
i.join()
print("ending....")
递归锁 为了支持在同一线程中多次请求同一资源,python提供了“可重入锁”:threading.RLock。RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源。
import threading
import time
class MyThread(threading.Thread):
def actionA(self):
r_lcok.acquire() #count=1
print(self.name,"gotA",time.ctime())
time.sleep(2)
r_lcok.acquire() #count=2
print(self.name, "gotB", time.ctime())
time.sleep(1)
r_lcok.release() #count=1
r_lcok.release() #count=0
def actionB(self):
r_lcok.acquire()
print(self.name, "gotB", time.ctime())
time.sleep(2)
r_lcok.acquire()
print(self.name, "gotA", time.ctime())
time.sleep(1)
r_lcok.release()
r_lcok.release()
def run(self):
self.actionA()
self.actionB()
if __name__ == '__main__':
# A=threading.Lock()
# B=threading.Lock()
r_lcok=threading.RLock()
L=[]
for i in range(5):
t=MyThread()
t.start()
L.append(t)
for i in L:
i.join()
print("ending....")
--->>>
Thread-1 gotA Tue Jun 18 23:43:40 2019
Thread-1 gotB Tue Jun 18 23:43:42 2019
Thread-1 gotB Tue Jun 18 23:43:43 2019
Thread-1 gotA Tue Jun 18 23:43:45 2019
Thread-3 gotA Tue Jun 18 23:43:46 2019
Thread-3 gotB Tue Jun 18 23:43:48 2019
Thread-3 gotB Tue Jun 18 23:43:49 2019
Thread-3 gotA Tue Jun 18 23:43:51 2019
Thread-5 gotA Tue Jun 18 23:43:52 2019
Thread-5 gotB Tue Jun 18 23:43:54 2019
Thread-2 gotA Tue Jun 18 23:43:55 2019
Thread-2 gotB Tue Jun 18 23:43:57 2019
Thread-2 gotB Tue Jun 18 23:43:58 2019
Thread-2 gotA Tue Jun 18 23:44:00 2019
Thread-5 gotB Tue Jun 18 23:44:01 2019
Thread-5 gotA Tue Jun 18 23:44:03 2019
Thread-4 gotA Tue Jun 18 23:44:04 2019
Thread-4 gotB Tue Jun 18 23:44:06 2019
Thread-4 gotB Tue Jun 18 23:44:07 2019
Thread-4 gotA Tue Jun 18 23:44:09 2019