Python中并发编程之threading模块方法以及线程概念

线程的概念

能被操作系统调度(给CPU执行)的最小单位

同一个进程中的多个线程可以同时被CPU执行

数据共享,操作系统调度的最小单位,可以利用多核,由操作系统调度,数据不安全

开启关闭时间消耗小(比进程小很多)

数据不安全

def a():
    for i in range(1000000):
        global count
        count+=1

def b():
    for i in range(1000000):
        global count
        count-=1

count=0

t1=Thread(target=a)
t2=Thread(target=b)
t1.start()
t2.start()
t1.join()
t2.join()

print(count)#此时的count不等于0

数据不安全的本质是因为+=操作在CPU指令层面是两个操作(计算和赋值)
在这里插入图片描述
在执行加法操作后GIL将CPU的权限轮转给下一个线程,当前线程会等待下次执行时间片到来然后再将计算后的值 1 赋值回a
下一个线程在取数据时就会出现问题
因为上一个线程未将实现加法后的值赋值回去所以这个线程中a还是=0
计算赋值结束后a=1就与上个线程重复了
线程的切换跟执行非常非常快,所以开启巨量线程时会导致数据不安全的问题出现
解决方法就是下文中的互斥锁Lock

Python当中的多线程

from threading import Thread

全局解释器锁 GIL (global interpreter lock)

全局解释器锁的出现是为了完成gc的回收机制,对不同线程的引用计数的变化记录更加精确

全局解释器锁导致了同一个进程中的多个线程只能有一个线程真正被执行

主要节省的是I/O操作的时间,而不是CPU计算的时间,因为CPU的计算速度非常快,部分情况下,我们没有办法把一条进程中所有的I/O操作都规避掉

Cpython解释器 无法利用多核

在Cpython解释器中的py多线程本质上还是串行

gc 垃圾回收机制 线程

引用计数 + 分代回收

pypy 解释器 gc 不能利用多核
jpython解释器 能利用多核

threading模块开启线程

def start_function(n):
	print(n)

t=Thread(target=start_function,args=(n,))
#target=函数名,args=(arg1,arg2,arg3,arg4)传入的参数需为一个元组类型

t.start()#开启线程

所有线程不能杀死,只能等待执行完毕

current_thread()获取到当前线程的对象,通过 .ident可以获取到线程的id

enumerate 列表 存储了所有活着的线程对象 包括主线程

active_count() 数字 ,存储了所有活着的线程对象的个数

阻塞线程

def func():
	print(1)
t=Thread(target=func).start()
t.join()#阻塞,等待线程执行完毕再接着进行主进程下面的代码
print('done')

阻塞多个或所有进程

每开启一个线程时将该线程实例化后产生的对象存入主进程的一个列表中
在所有线程开启完后,遍历线程列表,使用join方法阻塞

thread_list=[]
def func():
	pass
for i in range(100):
	t=Tread(target=func)
	thread_list.append(t)
	t.start()
for i in thread_list:
	i.join()
在子线程中current_thread()可以获取到当前线程的对象,通过 .ident()可以获取到线程的id
在主进程中active_count() 返回数字 ,存储了所有活着的线程对象的个数

守护线程的概念

主进程会等待子线程结束之后才结束

守护线程随着主线程的结束而结束

守护线程会在主线程的代码结束之后继续守护其他子线程

守护线程与守护进程的区别

守护进程和守护线程的结束原理不同

守护进程需要主进程来回收资源

守护线程是随着进程的结束才结束的

其他子线程–>主线程结束–>主进程结束–>整个进程中所有的资源都被回收–>守护线程也会被回收

守护进程

守护进程 会随着主进程的代码结束而结束

如果主进程代码结束之后还有其他子进程在运行,守护进程不守护

守护线程

守护线程 随着主线程的结束而结束

如果主线程代码结束之后还有其他子线程在运行,守护线程也守护

进程是资源分配单位
子进程都需要它的父进程来回收资源
线程是进程中的资源
所有的线程都会随着进程的结束而被回收的

Python开启守护进程的方法

在实例化线程对象时添加一个daemon=True的参数即可

def a(n):
	time.sleep(n)

t1= Thread(target=a,args=(10,)).start()

t2= Thread(target=a,args=(1,),daemon=True).start()

#此时t2会等待t1执行完毕才关闭

线程锁

互斥锁 Lock

from threading import Lock

当多个线程需要同时共用某一样东西时,因为线程之间数据共享且数据不安全所以需要加锁来确保数据安全
在操作数据前获取锁
在操作数据结束后释放锁
所有线程需要共用一个主进程的锁否则锁会失效
当下一个线程获取不到锁时会阻塞,等待上一个线程释放锁之后获取并运行

from threading import Lock
lock=Lock()#实例化一个锁

def a(lock):
    for i in range(1000000):
        global count
        lock.acquire()#获取锁
        count+=1
        lock.release()#释放锁

def b(lock):
    for i in range(1000000):
        global count
        lock.acquire()#获取锁
        count-=1
        lock.release()#释放锁
        
count=0

t1=Thread(target=a,args=(lock,))
t2=Thread(target=b,args=(lock,))

t1.start()
t2.start()

t1.join()
t2.join()


print(count)#此时count==0


Rlock 递归锁 (recursion lock)

当一个线程必须获得两个或多个文件的操作权限才能进行操作时,使用递归锁将所有文件锁起来防止其他线程来操作
rlock=RLock()

rlock.acquire()

rlock.release()

在同一个线程中可以被多次acquire
Lock效率比Rlock效率高

死锁现象

多把锁,并且在多个线程中交叉使用

如果是互斥锁,出现了死锁现象,最快速的解决方式是把所有锁都改成一把递归锁

程序的效率会降低

递归锁 效率低 但是解决死锁现象有奇效

互斥锁 效率高 但是多把锁容易出现死锁现象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值