一、并发编程之多线程
1、线程简单介绍
进程是资源单位,把所有资源集中到一起,而线程是执行单位,真正执行的是线程
每个进程都有一个地址空间,而且默认就有一个控制线程
多线程:在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间。进程之间是竞争关系,线程之间是协作关系
线程的创建开销比进程小很多,运行较快
主线程从执行层面上代表了其所在进程的执行过程
2、线程开启方式
方式一:使用替换threading模块提供的Thread
from threading import Thread
def task():
print(‘is running‘)
if __name__ == ‘__main__‘:
t=Thread(target=task,)
t.start()
print(‘主‘)
方式二:自定义类,继承Thread
from threading import Thread
class MyThread(Thread):
def __init__(self,name):
super().__init__()
self.name=name
def run(self):
print(‘%s is running‘ %self.name)
if __name__ == ‘__main__‘:
t=MyThread(‘egon‘)
t.start()
print(‘主‘)
3、线程与进程的pid
from threading import Thread
from multiprocessing import Process
import os
def task():
print(‘%s is running‘ %os.getpid())
if __name__ == ‘__main__‘:
# t1=Thread(target=task,) #在主进程下开启多个线程,每个线程都跟主进程的pid一样
# t2=Thread(target=task,)
t1=Process(target=task,) #开启多个进程,每个进程都有不同的pid
t2=Process(target=task,)
t1.start()
t2.start()
print(‘主‘,os.getpid())
4、多线程共享同一个进程内的资源
from threading import Thread
from multiprocessing import Process
n=100
def work():
global n
n=0
if __name__ == ‘__main__‘:
# p=Process(target=work,)
# p.start()
# p.join()
# print(‘主‘,n) #子进程p已经将自己的全局的n改成了0,但改的是它自己的,父进程仍然是100
t=Thread(target=work,)
t.start()
t.join()
print(‘主‘,n) #查看结果为0,同一进程内的线程之间共享进程内的数据
5、Thread对象其他相关的属性或方法
isAlive():返回线程是否活动
getName():返回线程名
setName():设置线程名
current_thread的用法
from threading import Thread,activeCount,enumerate,current_thread
from multiprocessing import Process
import time
def task():
print(‘%s is running‘ %current_thread().getName())
time.sleep(2)
if __name__ == ‘__main__‘:
p=Process(target=task)
p.start()
print(current_thread())
6、守护线程
进程跟线程都会遵循:守护xxx会等待主xxx运行完毕后被销毁
主进程:运行完毕指的是主进程代码运行完毕
主线程:运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
注意:运行完毕并非终止运行
from threading import Thread
import time
def task1():
print(‘123‘)
time.sleep(10)
print(‘123done‘)
def task2():
print(‘456‘)
time.sleep(1)
print(‘456done‘)
if __name__ == ‘__main__‘:
t1=Thread(target=task1)
t2=Thread(target=task2)
t1.daemon=True
t1.start()
t2.start()
print(‘主‘)
7、GIL全局解释器锁
GIL本质是一把互斥锁,将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。
在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势
from threading import Thread
n=100
def task():
print(‘is running‘)
if __name__ == ‘__main__‘:
t1=Thread(target=task,)
t2=Thread(target=task,)
t3=Thread(target=task,)
# t=Process(target=task,)
t1.start()
t2.start()
t3.start()
print(‘主‘)
多个线程先访问到解释器的代码,即拿到执行权限,然后将target的代码交给解释器的代码去执行
解释器的代码是所有线程共享的,所以垃圾回收线程也可能访问到解释器的代码而去执行,这就导致了一个问题:对于同一个数据100,可能线程1执行n=100的同时,而垃圾回收执行的是回收100的操作,解决这种问题没有什么高明的方法,就是加锁处理,如下图的GIL,保证python解释器同一时间只能执行一个任务的代码
结论:
1、如果是I/O密集型的,可以开启多线程
2、如果是要求计算性能,可以利用多核,开启进程
8、同步锁
1、线程抢的是CIL锁,GIL锁相当于执行权限,拿到执行权限后才能拿到互斥锁lock,其他线程也可以抢到GIL,但如果发现lock仍然没有被释放则阻塞,即便是拿到执行权限GIL也要立刻交出来
2、jion是等待所有,即整体串行,而锁只是锁住修改共享数据的部分,即部分串行,要想保住数据安全性的根本原来在于让并发变成串行,jion与互斥锁都可以实现,互斥锁的部分串行效率更高