1、开启线程
一、什么是线程:
1.进程是资源分配的最小单位,线程是CPU调度的最小单位。每一个进程中至少有一个线程。
2.主进程中的线程称为主线程,其他开启的线程称为子线程
二、为什么用线程:
进程有两个缺点:
1.进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
2.进程在执行的过程中如果遇到阻塞,例如等待输入,整个过程就会挂起。即使进程中有些工作不依赖于输入的数据,也将无法执行。
# 而这就是为什么还要用线程
代码:
from threading import Thread
def task():
print('子线程')
if __name__ == '__main__':
t = Thread(target=task)
t.start()
print('主线程')
2、GIL全局解释器锁
GIL全局解释器锁的特点:
1.python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。
2.虽然python解释器可以“运行”多个线程,但在任意时刻只有一个线程在解释器中执行。
3.由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
常识:
1.python代码在解释器中执行,绝大部分在用:cpython解释器,少部分用:pypy解释器
2.GIL锁在解释器中存在,他只能在cpython解释器中,pypy解释器中不存在
3.起一个垃圾回收线程,起一个正常执行的线程,垃圾回收还没有回收完毕,另一个线程可能会抢占资源
4.设置一把全局解释器锁(GIL锁),有了这把锁,就保证同一时刻,只能有一个线程执行,只要线程想执行,那么,就必须拿到这把GIL锁
5.如果是IO密集型:选择线程,因为io不会用到cpu内核,线程在进程里了
6.如果是计算密集型:选择进程 # 计算密集型用多进程,计算io密集型用多线程
3、进程和线程的区别
一、开启效率的较量
from multiprocessing import Process
from threading import Thread
import time
# 开启进程的时间
def task():
time.sleep(1)
print(123)
if __name__ == '__main__':
ctime = time.time()
p = Process(target=task)
p.start()
p.join()
print(time.time() - ctime)
# 输出结果:1.0815215110778809
# 开启线程的时间
def task():
time.sleep(1)
print(123)
if __name__ == '__main__':
ctime = time.time()
t = Thread(target=task)
t.start()
t.join()
print(time.time() - ctime)
# 输出结果:1.0080914497375488
# 结论:进程消耗比线程消耗大很多
二、多线程和多进程进行pid的比较
# 进行pid的较量
from multiprocessing import Process
from threading import Thread
import os
def task():
print('hello',os.getpid())
if __name__ == '__main__':
# 注:开启多个进程,每个进程都有不同的pid
p1 = Process(target=task)
p2 = Process(target=task)
p1.start()
p2.start()
print('主进程/主进程pid',os.getpid())
# 注:在主进程下开启多个线程,每个线程都跟主进程pid一样
t1 = Thread(target=task)
t2 = Thread(target=task)
t1.start()
t2.start()
print('主进程/主进程pid',os.getpid())
# 结论:开启多进程,每个进程的pid都是不同的。在主程序下开启多线程,每个线程pid都和主程序pid一样。
4、Thread类的其他方法
一、Thread实例对象的方法:
1.is_alive():返回线程是否活动
2.getName();返回线程的名称
3.setName():设置线程名称
二、threading模块提供的一些方法:
1.threading.currentThread():返回当前的线程变量
2.threading.enumerate():返回一个包含正在运行的线程的list。
3.threading.activeCount():范慧慧正在运行的线程数量,与len(threading.enumerate())有相同的结果。
代码:
from threading import Thread
import time
def task():
time.sleep(1)
print(123)
if __name__ == '__main__':
t = Thread(target=task)
t.start()
print(t.is_alive()) # 返回线程是否活动
print(t.getName()) # 返回线程的名称
t.setName('mmm') # 设置线程的名称
print(t.getName())
5、守护线程
p.isDaemon和t.setDaemon(True)的特点:主进程(线程)结束,子进程(线程)也随之结束。也根本不会运行子进程(线程)的程序。# 注意守护进程一定要放在start()之前,不然不会运行
代码:
from threading import Thread
import time
import os
def task():
time.sleep(1)
print('my is %s'%(os.getpid()))
if __name__ == '__main__':
t =Thread(target=task)
t.setDaemon(True) # 跟进程不一样,进程的是p.isDaemon
t.start()
print('主线程')
print(t.is_alive())
# 返回结果:主线程,True,特点主线程结束,子线程也结束。
6、同步锁(互斥锁)
代码:
from threading import Thread,Lock
import time
def task(lock):
global n
lock.acquire() # 上锁
time.sleep(1)
n -= 1
lock.release() # 释放锁
if __name__ == '__main__':
n = 10
l = []
lock = Lock()
for i in range(10):
t = Thread(target=task,args=(lock, ))
t.start()
l.append(t)
for j in l:
j.join()
print(n) # 结果肯定为0,由原来的并发执行变成串行,牺牲了执行效率保证了数据安全
# 补充:Lock在Process中为进程锁,在Thread中为互斥锁。
7、信息量(Semaphore)
# Semaphore:信息量可以理解为多把锁,同时允许多个进程量更改数据
代码:
from threading import Thread,Semaphore
import time
import random
sm = Semaphore(2) # 允许两个线程
def task(i):
sm.acquire()
print('线程:%s进来了'%i)
time.sleep(random.randint(1,3))
print('线程:%s出去了'% i)
sm.release()
if __name__ == '__main__':
for i in range(6):
t = Thread(target=task,args=(i, ))
t.start()
8、Enent事件
Enent事件的用法:
1.一些线程需要等到其他线程执行完成之后才能执行,类似于发射信号
2.比如一个线程等待另一个线程执行结束在继续执行
代码:
from threading import Thread, Event
import time
def girl(event):
print('女神正在谈恋爱中')
time.sleep(3)
event.set() # 发射信号
print('女神分手了')
def boy(i, event):
print('屌丝:%s正在等待女神分手' % i)
event.wait() # 正在等待
print('屌丝:%s开始展开追求' % i)
if __name__ == '__main__':
event = Event()
t = Thread(target=girl, args=(event,))
t.start()
for i in range(5):
t1 = Thread(target=boy, args=(i, event))
t1.start()