python 多线程
示例1
import threading
import time
# 进程比较消耗资源 线程比较消耗cpu
def func01(number):
print("函数func01 start")
time.sleep(2)
print("函数func01 end")
def func02(number):
print("函数func02 start")
time.sleep(2)
print("函数func02 end")
if __name__ == '__main__':
# 创建两个线程 传入执行的函数
thread01 = threading.Thread(target=func01,args=(1,))
thread02 = threading.Thread(target=func02,args=(2,))
begin_time = time.time()
# 执行线程
thread01.start()
thread02.start()
ent_time = time.time()
print("耗时:",ent_time-begin_time)
线程守护
如果有一个线程必须设置为循环,那么该线程不结束,意味着整个python程序就不能结束,那为了能够让python程序正常退出,将这类循环的线程设置为守护线程,当程序当中仅仅剩下守护线程时,python程序就能够正常退出,不必关心这类线程是否执行完毕。
2、作用
守护线程作用是为其他线程提供便利服务,守护线程最典型的应用就是 GC (垃圾收集器)。
3、使用注意
(1)守护线程会在“该进程内所有非守护线程全部都运行完毕后,守护线程才会挂掉”
(2)主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收
子线程默认为【非守护线程】,主线程代码执行完毕,各子线程继续运行,直到所有【非守护线程】结束,python程序退出。
我们在对线程进行运行时,如果其中的子线程不结束,会导致整个程序不能停止。但如果在其中加入了守护线程就不一样了,从名称就可以看出是保护整个线程的,所以程序可以放心的退出了。
import threading
import time
# 进程比较消耗资源 线程比较消耗cpu
def func01(number):
print("函数func01 start")
time.sleep(2)
print("函数func01 end")
def func02(number):
print("函数func02 start")
# 把线程2 设置等待4秒和 线程1做区分
time.sleep(4)
print("函数func02 end")
if __name__ == '__main__':
# 创建两个线程 传入执行的函数
thread01 = threading.Thread(target=func01,args=(1,))
thread02 = threading.Thread(target=func02,args=(2,))
begin_time = time.time()
# 为线程2 设置守护线程 当主线程退出时 子线程thread02 同时也kill
thread02.setDaemon(True)
# 执行线程
thread01.start()
thread02.start()
ent_time = time.time()
print("耗时:",ent_time-begin_time)
主线程等待子线程完成,阻塞主线程
import threading
import time
# 进程比较消耗资源 线程比较消耗cpu
def func01(number):
print("函数func01 start")
time.sleep(2)
print("函数func01 end")
def func02(number):
print("函数func02 start")
# 把线程2 设置等待4秒和 线程1做区分
time.sleep(4)
print("函数func02 end")
if __name__ == '__main__':
# 创建两个线程 传入执行的函数
thread01 = threading.Thread(target=func01,args=(1,))
thread02 = threading.Thread(target=func02,args=(2,))
begin_time = time.time()
# 为线程2 设置守护线程 当主线程退出时 子线程thread02 同时也kill
thread02.setDaemon(True)
# 执行线程
thread01.start()
thread02.start()
# 设置线程阻塞 只要当子线程全部执行完,在切到主线程上
thread01.join()
thread02.join()
ent_time = time.time()
print("耗时:",ent_time-begin_time)
用类的方法管理多线程
import threading
import time
# 进程比较消耗资源 线程比较消耗cpu
class Stu01(threading.Thread):
def __init__(self,name):
super().__init__(name=name)
# 重载run方法 实现业务逻辑
def run(self):
print("类stu01 start")
time.sleep(2)
print("类stu01 end")
class Stu02(threading.Thread):
def __init__(self,name):
super().__init__(name=name)
# 重载run方法 实现业务逻辑
def run(self):
print("类stu02 start")
time.sleep(2)
print("类stu02 end")
if __name__ == '__main__':
# 创建两个线程 传入执行的函数
thread01 = Stu01("小米")
thread02 = Stu02("小红")
begin_time = time.time()
# 为线程2 设置守护线程 当主线程退出时 子线程thread02 同时也kill
thread02.setDaemon(True)
# 执行线程
thread01.start()
thread02.start()
# 设置线程阻塞 只要当子线程全部执行完,在切到主线程上
thread01.join()
thread02.join()
ent_time = time.time()
print("耗时:",ent_time-begin_time)
线程间通信
共享变量
线程不太安全
import threading
import time
# 进程比较消耗资源 线程比较消耗cpu
# 线程间通信方式 共享变量
# 先定义一个全局变量
detail_url_list = []
def get_detail_html(detail_url_list):
# 爬取文章详情页
# global detail_url_list
while True:
if len(detail_url_list):
url = detail_url_list.pop()
print("函数func01 start")
time.sleep(2)
print("函数func01 end")
def get_deatil_url(detail_url_list):
# 爬取文章列表页
# global detail_url_list
while 1:
print("函数func02 start")
time.sleep(4)
for i in range(20):
detail_url_list.append("http://projectsedu.com/{id}".format(id=i))
print("函数func02 end")
if __name__ == '__main__':
begin_time = time.time()
thread_detail_url = threading.Thread(target=get_deatil_url,args=(detail_url_list,))
for i in range(5):
html_thread = threading.Thread(target=get_detail_html,args=(detail_url_list,))
html_thread.start()
thread_detail_url.start()
ent_time = time.time()
print("耗时:",ent_time-begin_time)
通过消息队列queue
import threading
import time
# 通过queue 消息队列 的方式进行线程间同步
from queue import Queue
# 线程间通信
def get_detail_html(queue):
# 爬取文章详情页
# global detail_url_list
while True:
# 是阻塞的,如果队列为空会停止等待
url = queue.get()
print("函数func01 start")
time.sleep(2)
print("函数func01 end")
def get_deatil_url(queue):
# 爬取文章列表页
# global detail_url_list
while 1:
print("函数func02 start")
time.sleep(4)
for i in range(20):
queue.put("http://projectsedu.com/{id}".format(id=i))
print("函数func02 end")
if __name__ == '__main__':
begin_time = time.time()
# 定义最大值
detail_url_queue = Queue(maxsize=1000)
thread_detail_url = threading.Thread(target=get_deatil_url, args=(detail_url_queue,))
for i in range(5):
html_thread = threading.Thread(target=get_detail_html, args=(detail_url_queue,))
html_thread.start()
thread_detail_url.start()
# 阻塞 等待执行完成在切换到主线程
detail_url_queue.task_done()
detail_url_queue.join()
ent_time = time.time()
print("耗时:", ent_time - begin_time)
线程同步
加锁lock
from threading import Lock
# 线程间同步
# 定义一把锁
lock = Lock()
# 1、锁会有一定影响并发性能 ,因为获取锁和释放锁 都需要时间
# 2、锁会影响死锁
"""
A(a,b)
acquire(a) A线程加锁先获取a资源
acquire(b) 在获取b资源才能释放锁
B(b,a)
acquire(a) B线程加锁先获取b资源
acquire(b) 在获取a资源才能释放锁
# 这样交叉资源竞争等待对方释放锁 ,就会陷入死循环
在举例
def add(lock):
global total
for i in range(1000):
lock.acquire() # 获取锁
dosomething(lock) # 传入锁后 因为上了两次 里面等外面释放,而外面代码要执行 必须等里面结束 ,所以会死锁
total += 1
lock.release() # 释放锁 如果不释放 其他代码块无法执行
def dosomething(lock):
lock.acquire()
print("do something")
lock.release()
"""
total = 0
def add():
global total
global lock
for i in range(1000):
lock.acquire() # 获取锁
total += 1
lock.release() # 释放锁 如果不释放 其他代码块无法执行
def desc():
global total
global lock
for i in range(1000):
lock.acquire() # 获取锁
total -= 1
lock.release() # 释放锁
import threading
thread1 = threading.Thread(target=add)
thread2 = threading.Thread(target=desc)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print(total)
可重复锁RLock
from threading import Lock,RLock # 可重入锁
# 在同一个线程里面,可以连续调用多次acquire
# 一定要注意acquire 的次数要和release的次数相等
# 线程间同步
# 定义一把锁
lock = RLock()
# 1、锁会有一定影响并发性能 ,因为获取锁和释放锁 都需要时间
total = 0
def add():
global total
global lock
for i in range(1000):
lock.acquire() # 获取锁
lock.acquire() # 获取锁
total += 1
lock.release() # 释放锁 如果不释放 其他代码块无法执行
lock.release() # 释放锁
def desc():
global total
global lock
for i in range(1000):
lock.acquire() # 获取锁
total -= 1
lock.release() # 释放锁
import threading
thread1 = threading.Thread(target=add)
thread2 = threading.Thread(target=desc)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print(total)
条件锁condition
import threading
# 条件变量 针对复杂的线程同步
"""
天猫精灵: 小爱同学
小爱:在
天猫精灵:我们来对古诗吧
小爱:好啊
天猫精灵:我住长江头
小爱:君住长江尾
天猫精灵:日日思君不见君
小爱:共饮长江水
天猫精灵:此水几时休
小爱:此恨何时己
天猫精灵:只愿君心似我心
小爱:定不负相思意
"""
# 通过condittion 完成协同读诗
class XiaoAi(threading.Thread):
def __init__(self,cond):
super().__init__(name="小爱")
self.cond =cond
def run(self):
with self.cond:
self.cond.wait() # 等待 通知
print("{}:在".format(self.name))
self.cond.notify() # 通知 天猫精灵
self.cond.wait() # 等待 通知
print("{}:好啊".format(self.name))
self.cond.notify() # 通知 天猫精灵
self.cond.wait() # 等待 通知
print("{}:君住长江尾".format(self.name))
self.cond.notify() # 通知 天猫精灵
self.cond.wait() # 等待 通知
print("{}:共饮长江水".format(self.name))
self.cond.notify() # 通知 天猫精灵
self.cond.wait() # 等待 通知
print("{}:此恨何时己".format(self.name))
self.cond.notify() # 通知 天猫精灵
self.cond.wait() # 等待 通知
print("{}:定不负相思意".format(self.name))
self.cond.notify() # 通知 天猫精灵
class TianMao(threading.Thread):
def __init__(self, cond):
super().__init__(name="天猫精灵")
self.cond = cond
def run(self):
with self.cond:
print("{} :小爱同学".format(self.name))
self.cond.notify() # 通知 小爱
self.cond.wait() # 等待 小爱通知
print("{} :我们来对古诗吧".format(self.name))
self.cond.notify() # 通知 小爱
self.cond.wait() # 等待小爱通知
print("{} :我住长江头".format(self.name))
self.cond.notify() # 通知 小爱
self.cond.wait() # 等待小爱通知
print("{} :日日思君不见君".format(self.name))
self.cond.notify() # 通知 小爱
self.cond.wait() # 等待小爱通知
print("{} :此水几时休".format(self.name))
self.cond.notify() # 通知 小爱
self.cond.wait() # 等待小爱通知
print("{} :定不负相思意".format(self.name))
self.cond.notify() # 通知 小爱
self.cond.wait() # 等待小爱通知
print("{} :只愿君心似我心".format(self.name))
self.cond.notify() # 通知 小爱
self.cond.wait() # 等待小爱通知
if __name__ == '__main__':
cond = threading.Condition()
xiaoai = XiaoAi(cond)
tianmao = TianMao(cond)
# 启动顺序很重要
# 在调用with cond之后才能调用wait或者notfy方法
# condition 有两层锁,一把底层锁会在线程调用了wait方法的时候释放
# 上面的锁会在每次调用wait的时候分配一把并放入到cond的等待队列中,等待notify方法的唤醒
xiaoai.start()
tianmao.start()
Semaphore 控制并发数量
import threading
import time
# 用于控制进入数量的锁
class HtmlSpider(threading.Thread):
def __init__(self,url,sem):
super().__init__()
self.url = url
self.sem =sem
def run(self):
time.sleep(2)
print("got html text success")
self.sem.release()
class UrlProducer(threading.Thread):
def __init__(self,sem):
super().__init__()
self.sem = sem
def run(self):
for i in range(20):
self.sem.acquire()
html_thread = HtmlSpider("http://baidu.com/{}".format(i),self.sem)
html_thread.start()
if __name__ == '__main__':
# 控制并发次数
sem = threading.Semaphore(3)
url_producer = UrlProducer(sem)
url_producer.start()
浙公网安备 33010602011771号