0.关于进程、线程
由于我们的CPU 核数有限不同同时处理过多的任务,但实际生活中我们却可以一边听歌一边浏览网页还能同时使用QQ聊天,这就用到了伪并发处理。
即CPU处理任务时在多个进程之间快速切换,在极短时间内,CPU处理任务时还是一项一项完成,当以1s为度量的时候,我们的直观感受上是同处理、同时进行。
线程是进程的一个执行单元,比进程更小的独立运行的基本单位。
一个程序至少一个进程,一个进程至少一个线程。
1.了解线程
线程也叫轻量级进程,是操作系统能够进行运算调度的最小单位,包含在进程之中,是进程中实际运作的单位。自己不拥有系统资源,只拥有一点自己在运行中必要的资源。可以与同一个进程下的其他线程共享所拥有的全部资源。一个线程可以创建和撤销另一个线程,同一进程中的多个线程可以并发执行。
2.使用线程的优点
- 进程之间不能共享内存,但线程之间共享内存非常容易。
- 操作系统在创建进程时,需要为该进程重新分配系统资源,但创建线程的代价则小得多。因此,使用多线程来实现多任务并发执行比使用多进程的效率高。
- Python 语言内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了 Python 的多线程编程。
3.线程实现
3.1普通创建方式
#导入模块
import threading
import time
#定义两个函数,一个为加函数,一个为乘函数
def fun1(a,b):
print('this is fun1')
time.sleep(1)
print(a+b,end='\n')
def fun2(a,b):
print('this is fun2')
time.sleep(1)
print(a*b)
a,b= 3,4
#这里开始创建两个线程分别为t1,t2,target表示线程要执行的函数,args表示被执行函数的参数
t1 = threading.Thread(target=fun1,args =(a,b))
t2 = threading.Thread(target=fun2, args = (a,b))
#启动线程,如果不启动,则线程不会执行
t1.start()
t2.start()
3.2 自定义线程
继承于threading.Thread来自定义线程类,本质为重构Thread 类中的run方法
import threading
import time as t
class Mythread(threading.Thread):
# 类的初始化(相当于C++中的构造函数)
def __init__(self,a=0,b=0,c=0,d=0):
super(Mythread,self).__init__()#重构run函数必须写,处理基类初始化
self.a = a
self.b = b
self.c = c
self.d = d
# run函数是我们要重构的函数,函数只有一个参数self,所有的变量在初始化中赋值,这里仅负责处理数据
def run(self):
if self.a%2==0:
print('this is a threading+++',end='\n')
t.sleep(1)
print(self.a+self.b+self.c+self.d,end='\n')
else:
print('this is a threading ***')
t.sleep(1)
if self.b==0:self.b=1
if self.c==0:self.c=1
if self.d==0:self.d=1
print(self.a*self.b*self.c*self.d,end='\n')
# 创建线程
t1 = Mythread(1,2,4)
t2 = Mythread(2,3)
# 启动线程
t1.start()
t2.start()
以下也是一个自定义线程的例子,由此可见,重写def __init__函数不是必须的。
但是如果需要参数的话,必须用第一种!!!
import time,threading
# 自定的线程类
class MyThread(threading.Thread):
def actionA(self):
print('this is A')
def actionB(self):
print("this is B")
def run(self):
self.actionA()
self.actionB()
if __name__ == '__main__':
t = MyThread()
t.start()
3.3守护线程
使用setDaemon(Ture)把所有的子线程都变成了主线程的守护线程,因此只有当主进程结束的时候子线程才会随之结束。
注意:守护线程是随着主进程的结束而结束,主进程不会去管子线程的任务有没有完成,而子线程必须跟随主进程结束。而进程中没有非守护线程存在的时候才会结束
import threading
import time
def fun(n):
print('我下面有个sleep,我想让程序多跑会,可是进程不让啊')
time.sleep(2)
print('要是我能成功执行完,我就说一声吧')
if __name__ == '__main__':
t = threading.Thread(target=fun, args=("t1",))
t.setDaemon(True) #把子线程设置为守护线程,必须在start()之前设置
t.start()
print("我是进程,我要让我的守护线程都结束!")
3.4主线程等待子线程结束
为了让守护线程执行结束后,主进程在结束,可以使用join方法让主线程等待子线程执行完毕。
即等待有join的线程结束后再执行后面的 语句。
import threading
import time
def fun(n):
print('我下面有个sleep,我想让程序多跑会,可是进程不让啊')
time.sleep(2)
print('要是我能成功执行完,我就说一声吧')
if __name__ == '__main__':
t = threading.Thread(target=fun, args=("t1",))
t.setDaemon(True) #把子线程设置为守护线程,必须在start()之前设置
t.start()
t.join() # 设置主线程等待子线程结束
print("我是进程,不过这次我要等我的进程们")
注意:守护线程的定义要在线程的之前定义,如果要让进程等待子线程的结束,则要在线程开启之后再进行设置。
3.5多线程共享全局变量
import threading as th
import time
glod_num = 5
def A():
global glod_num # 声明为全局变量
for i in range(2):
glod_num += i
print("I'm A ,I get %d glod to glod_num,now ,glod_num have %d"%(i,glod_num))
def B():
global glod_num #声明为全局变量
for i in range(2):
glod_num += i
print("I'm B ,I get %d glod to glod_num,now ,glod_num have %d"%(i,glod_num))
if __name__ == '__main__':
t1 = th.Thread(target=A)
t2 = th.Thread(target=B)
t1.start()
t2.start()
3.6互斥锁
import threading,time
glod_num=0
#仅进行计算,不改变glod_num的值
def b1(num):
global glod_num
glod_num += num
glod_num -= num
def b2(num):
for i in range(1000000):
b1(num)
if __name__ == '__main__':
t1=threading.Thread(target=b2,args=(5,))
t2=threading.Thread(target=b2,args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(glod_num)
上面的这个代码的输出结果是不确定的,可能会输出 正确结果0,也可能是其他值。
原因:在计算glod_num += num的时候是分两步计算:
-
先计算glod_num+num的值放入内存中。
-
将glod_num+num的结果赋值给glod_num。
程序启动了两个线程,此时glod_num变量又是进程内共享,所以当两个线程在‘同时’计算时,如果glod_num的变量没有完成第二步赋值的操作而被另外的线程刷新数据,结果就出现了错误。
为了解决这种问题,可以使用互斥锁(Lock)
一旦线程加锁,被锁的线程继续运行,没有被锁的线程暂时停止运行,知道被锁的线程被释放再次启动。
import threading as t
glod_num=0
lock = t.Lock() #实例化一个锁的对象
def b1(num):
global glod_num
glod_num += num
glod_num -= num
def b2(num):
for i in range(1000000):
lock.acquire() #等待获取或者获取修改变量的权限,并霸占它们
b1(num)
lock.release() #释放霸占的变量
if __name__ == '__main__':
t1=t.Thread(target=b2,args=(5,))
t2=t.Thread(target=b2,args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(glod_num)
3.7递归锁
死锁
import threading
import time
#死锁
class Threads(threading.Thread):
def actionA(self):
A.acquire()
print(self.name,"gotA",time.ctime())
time.sleep(1)
B.acquire()
print(self.name,"gotB",time.ctime(1))
time.sleep(2)
B.release()
A.release()
def actionB(self):
B.acquire()
print(self.name,"gotB",time.ctime())
time.sleep(1)
A.acquire()
print(self.name,"gotA",time.ctime(1))
time.sleep(2)
A.release()
B.release()
def run(self):
self.actionA()
self.actionB()
if __name__ == '__main__':
L=[]
A=threading.Lock()
B=threading.Lock()
for i in range(5):
t = Threads()
t.start()
L.append(t)
for i in L:
i.join()
print('ending****')
这个程序是对为什么引进递归锁很好的解释!!
首先我们分析下这个程序,当第一个线程执行完A函数的时候还没有问题,当第一个线程开启B函数时,因为此时第一线程的B函数刚准备执行,还没有加锁,第二个线程启动A函数,此时第一个线程上了B锁,第二个线程上A锁,程序被锁死,这时候就有必要引进递归锁!!
import threading
import time
class Threads(threading.Thread):
def actionA(self):
r_lock.acquire() #采用rlock
print(self.name,"gotA",time.ctime())
time.sleep(1)
r_lock.acquire()
print(self.name,"gotB",time.ctime(1))
time.sleep(1)
r_lock.release()
r_lock.release()
def actionB(self):
r_lock.acquire()
print(self.name,"gotB",time.ctime())
time.sleep(1)
r_lock.acquire()
print(self.name,"gotA",time.ctime(1))
time.sleep(2)
r_lock.release()
r_lock.release()
def run(self):
self.actionA()
self.actionB()
if __name__ == '__main__':
L=[]
# A=threading.Lock()
# B=threading.Lock()
r_lock = threading.RLock() #注意 采用的锁不同
for i in range(5):
t = Threads()
t.start()
L.append(t)
for i in L:
i.join()
print('ending****')
这里可以这样理解 r_lock类似于在C语言经典算法中的数组找最大值问题中的标志位sign,当r_lock的值决定了该线程是否有权力继续运作下去,一旦线程拿到锁就没有停止的理由,也可以说凡是拿到锁的线程拥有同等的权力去享受计算机资源,而没有拿到锁的线程就只能等待有锁的线程们释放之后再次准备竞争资源。