并发编程之线程
目录
并发编程之线程
一 什么是线程
计算机相当于大工厂,工厂里有一个个车间(进程) ,有很多人(线程) 干不同的事。
**进程是资源分配的最小单位,线程是CPU调度的最小单位。每一个进程中至少有一个线程。**
线程的开销更小,更轻量级。

二 开启线程的两种方式
1 通过函数的方式
# 第一种,通过函数
from threading import Thread
import time
def task():
print('start >>>>')
time.sleep(1)
print('end >>>>>')
if __name__ == '__main__':
t = Thread(target=task) # 实例化得到一个对象
t.start() # 对象.start()启动线程
print('>>>> main <<<<')
2 通过类继承的方式
# 第二种,通过类继承的方式
from threading import Thread
import time
class MyThread(Thread):
def run(self) -> None:
print('start >>>>')
time.sleep(1)
print('end >>>>>')
if __name__ == '__main__':
t = MyThread() # 若有参数,则需要定义类的__init__()方法
t.start()
print('>>>> main <<<<')
三 线程对象join方法
1 作用:等待子线程执行结束
2 代码示例:
# join的使用
import time
from threading import Thread
def task(n):
print('start >>>>')
time.sleep(n)
print('end >>>>>')
if __name__ == '__main__':
t = Thread(target=task,args=(2,))
t.start()
t1 = Thread(target=task,args=(3,))
t1.start()
t.join() # 等待子线程t执行结束再执行后续代码
print('>>>> main <<<<')
四 同一个进程下的多个线程数据共享
同一进程内的线程之间共享进程内的数据。
# 多线程下数据共享
from threading import Thread
import time
money = 999
def task(n):
global money
money = n
print('start >>>>')
# time.sleep(1)
print('end >>>>>')
if __name__ == '__main__':
t = Thread(target=task,args=(2,))
t.start()
t1 = Thread(target=task,args=(666,))
t1.start()
# t.join()
# t1.join()
print(money)
print('>>>> main <<<<')
"""
start >>>>
end >>>>>
start >>>>
end >>>>>
666
>>>> main <<<<
"""
五 线程对象及其他方法
1 重要的方法
1) t.name # 查看线程名字
2) t.getName() # 查看线程名字
3) active_count() # 查看当前进程下还有多少线程存活
4) t.is_alive() # 查看当前线程是否存活
5) t.ident # 当作是线程的id号
6) current_thread() # 相当于线程对象本身
2 代码示例
# 进程的其他方法
from threading import Thread, current_thread, active_count
import os, time
def task(n):
print('start >>>>')
print(current_thread().name) # 线程名字
print(os.getpid()) # 进程号
time.sleep(n)
print('end >>>>>')
if __name__ == '__main__':
t1 = Thread(target=task, name='lxx', args=(2,))
t2 = Thread(target=task, args=(8,))
t1.start()
t2.start()
t1.join()
print('---------', t1.is_alive())
print('---------', t2.is_alive())
print('*********', t1.ident) # 当作进程的id号
print('*********', t2.ident)
print(os.getpid())
print(active_count()) # 打印出2 ,开了两个线程,还有一个主线程,但是t1被join()阻塞已执行完毕
"""
start >>>>
lxx
23656
start >>>>
Thread-1
23656
end >>>>>
--------- False
--------- True
********* 16060
********* 27292
23656
2
end >>>>>
"""
六 守护线程
1 守护的原则
**无论是进程还是线程,都遵循:守护xxx会等待主xxx运行完毕后被销毁。**
2 注意:需要强调的是,运行完毕并非终止运行
#### (1) 对主进程来说:
运行完毕指的是主进程代码运行完毕;
**详细解释:**主进程在其他代码结束后就已经算运行完毕了(守护进程在此时就被回收) ,然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程) ,才会结束;
#### (2) 对主线程来说
运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕。
**详细解释:**主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收) 。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。
3 代码示例:
# 守护线程
from threading import Thread, active_count
import time
def task(n):
print('start >>>>')
time.sleep(n)
print('---------', active_count())
print('end >>>>>')
if __name__ == '__main__':
t1 = Thread(target=task, name='lxx', args=(10,))
t1.daemon = True
# t1.setDaemon(True)
t1.start()
t2 = Thread(target=task, name='lxx', args=(4,))
t2.start()
print('>>>> main <<<<')
"""
start >>>>
start >>>>
>>>> main <<<<
--------- 3
end >>>>>
"""
七 线程锁互斥
from threading import Thread, Lock
import time
money = 99
def task(mutex):
global money
mutex.acquire() # 在修改数据的时候加锁
temp = money
time.sleep(0.1)
money = temp -1
mutex.release() # 修改完毕后,释放锁,其他线程就能再次抢到锁
if __name__ == '__main__':
lll = []
mutex = Lock()
for i in range(10): # 模拟是个线程,都进行修改的操作
t = Thread(target=task,args=(mutex,))
t.start()
lll.append(t)
for i in lll: # 十个线程都进行阻塞调用,以得出最终的结果
i.join()
print(money)
八 GIL全局解释器锁理论
1 前提
1).python的解释器有很多,cpython,jpython,pypy(python写的解释器);
2).python的库多,库都是基于cpython写起来的,其他解释器没有那么多的库;
3).cpython中有一个全局大锁,每条线程要执行,必须获取到这个锁;
2 为何会有GIL全局解释器锁
为了实现python的垃圾回收机制;
3 GIL全局解释器锁的缺陷
1).某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行;
2).导致CPython中的多线程其实就是单线程;
4 总结
**1.python的多线程,不能利用多核优势**
cpython解释器中有一个全局锁(GIL) ,线程必须获取到GIL才能执行,我们开的多线程,不管有几个cpu,同一时刻,只有一个线程在执行。
**2.针对CPython解释器:**
①如果是io密集型操作:开多线程;
②如果是计算密集型:开多进程。

浙公网安备 33010602011771号