九、线程与进程
9.1 多线程基本使用
- 1.导入线程模块:
import threading
- 2.线程类Thread参数说明:Thread(lgroup [ target [, name [, args [, kwagl]]]]))
- group:线程组,目前只能使用None
- target:执行的目标任务名
- argsr 以元组的方式给执行任务传参
- kwargs: 以字典方式给执行任务传参
- name:线程名,-般不用设置
- daemon=True:设置为主线程的守护线程,主线程结束则子线程结束
方法 | 功能 |
---|---|
join() | 等待⼦线程结束之后,主线程继续执⾏ |
setDaemom() | 守护线程,不会等待⼦线程结束 |
threading.enumerate() | 查看当前线程的数量 |
start() | 创建并启动线程 |
import threading
import time
def dance():
#获取当前线程
current_thread=threading.current_thread()
print("dance:",current_thread)
for i in range(3):
print("跳舞中...")
time.sleep(0.2)
def sing():
#获取当前线程
current_thread=threading.current_thread()
print("sing:",current_thread)
for i in range(3):
print("唱歌中...")
time.sleep(0.2)
if __name__ == '__main__':
current_thread=threading.current_thread()
print("main_thread:",current_thread)
sing_thread=threading.Thread(target=sing)
sing_thread.start()
dance_thread=threading.Thread(target=dance,name='dance')
dance_thread.start()
9.2 线程的创建
-
直接创建:
threading.Thread(target=函数名)
-
通过继承Thread类创建线程
import threading import time class Main(threading.Thread): def run(self): for i in range(5): print(i) if __name__ == '__main__': m = Main() m.start()
9.3 查看线程数量
- threading.enumerate()
import threading
import time
def demo1():
for i in range(5):
print('demo1--%d'%i)
time.sleep(1)
def demo2():
for i in range(10):
print('demo2--%d' % i)
time.sleep(1)
def main():
t1 = threading.Thread(target=demo1,name = 'demo1')
t2 = threading.Thread(target=demo2,name = 'demo2')
t1.start()
t2.start()
while True:
print(threading.enumerate())
if len(threading.enumerate()) <= 1:
break
time.sleep(1)
if __name__ == '__main__':
main()
9.4 线程间的通信
- 在⼀个函数中,对全局变量进⾏修改的时候,是否要加global要看是否对全局变量的指向进⾏了修改,如果修改了指向,那么必须使⽤global,仅仅是修改了指向的空间中的数据,此时不⽤必须使⽤global线程是共享全局变量
- 多线程参数-args :
threading.Thread(target=test, args=(num,))
import threading
import time
# 线程间是共享的全局变量
# num = 100
num = [11,22]
def demo1(nums):
# global num
#
# num += 1
num.append(nums)
print('demo1--%s'% str(num))
def demo2():
print('demo2--%s' % str(num))
def main():
t1 = threading.Thread(target = demo1,args=(33,))
t2 = threading.Thread(target = demo2)
t1.start()
time.sleep(1) # 保证demo1先执行
t2.start()
print('main-num=%s'% str(num))
if __name__ == '__main__':
main()
9.5 线程间的资源竞争
- 查看CPU运行轨迹:
import dis
- 线程之间共享全局变量
import threading
import time
import dis # dis这个模块可以查看Python代码在CPU的运行轨迹
num = 0
def demo1(nums):
global num
for i in range(nums):
num += 1
print('demo1---%d'%num)
def demo2(nums):
global num
for i in range(nums):
num += 1
print('demo2---%d' % num)
def main():
t1 = threading.Thread(target=demo1,args=(1000000,))
t2 = threading.Thread(target=demo2,args=(1000000,))
t1.start()
t2.start()
time.sleep(3)
print('main---%d'%num)
if __name__ == '__main__':
main()
def add_num(num):
num += 1
print(dis.dis(add_num))
9.5.1 互斥锁
-
当多个线程⼏乎同时修改某⼀个共享数据的时候,需要进⾏同步控制某个线程要更改共享数据时,先将其锁定,此时资源的状态为"锁定",其他线程不能改变,只到该线程释放资源,将资源的状态变成"⾮锁定",其他的线程才能 再次锁定该资源。互斥锁保证了每次只有⼀个线程进⾏写⼊操作,从⽽保证了多线程情况下数据的正确性。
-
上锁:acquire()、解锁:release()
import threading import time # 创建一个互斥锁 # 互斥锁也叫做不可重复的锁。只能执行一次 # mutex = threading.Lock() mutex = threading.RLock() # 可以上多把锁,但是加锁和解锁的数量要一一对应 num = 0 def demo1(nums): global num # 上锁 mutex.acquire() mutex.acquire() for i in range(nums): num += 1 # 解锁 mutex.release() mutex.release() # mutex.release() print('demo1---%d'%num) def demo2(nums): global num # 上锁 mutex.acquire() for i in range(nums): num += 1 # 解锁 mutex.release() print('demo2---%d' % num) def main(): t1 = threading.Thread(target=demo1,args=(1000000,)) t2 = threading.Thread(target=demo2,args=(1000000,)) t1.start() t2.start()
9.5. 2死锁
-
在线程间共享多个资源的时候,如果两个线程分别占有⼀部分资源并且同时等
待对⽅的资源,就会造成死锁。
import threading import time class MyThread1(threading.Thread): def run(self): # 对mutexA上锁 mutexA.acquire() # mutexA上锁后,延时1秒,等待另外那个线程 把mutexB上锁 print(self.name+'----do1---up----') time.sleep(1) # 此时会堵塞,因为这个mutexB已经被另外的线程抢先上锁了 mutexB.acquire() print(self.name+'----do1---down----') mutexB.release() # 对mutexA解锁 mutexA.release() class MyThread2(threading.Thread): def run(self): # 对mutexB上锁 mutexB.acquire() # mutexB上锁后,延时1秒,等待另外那个线程 把mutexA上锁 print(self.name+'----do2---up----') time.sleep(1) # 此时会堵塞,因为这个mutexA已经被另外的线程抢先上锁了 mutexA.acquire() print(self.name+'----do2---down----') mutexA.release() # 对mutexB解锁 mutexB.release() mutexA = threading.Lock() mutexB = threading.Lock() if __name__ == '__main__': t1 = MyThread1() t2 = MyThread2() t1.start() t2.start()
9.6 Queue线程
-
在线程中,访问⼀些全局变量,加锁是⼀个经常的过程。如果你是想把⼀些数据存储到某个队列中,那么Python内置了⼀个线程安全的模块叫做queue模 块。Python中的queue模块中提供了同步的、线程安全的队列类,包括FIFO(先进先出)队列Queue,LIFO(后⼊先出)队列LifoQueue。这些队列 都实现了锁原语(可以理解为原⼦操作,即要么不做,要么都做完),能够在多线程中直接使⽤。可以使⽤队列来实现线程间的同步。
-
from multiprocessing import Queue #进程队列 from queue import Queue #普通线程队列
-
常用方法
方法 功能 Queue(maxsize) 创建⼀个先进先出的队列,并指定最大堆列数。 empty() 判断队列是否为空 qsize() 返回队列大小 get([block[, timeout]]) 获取队列,timeout等待时间 full() 判断队列是否满了 put() 将一个数据放到队列中 get_nowait() 相当get(False) put_nowait(item) 相当q.put(item, False) task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号 join() 实际上意味着等到队列为空,再执行别的操作 9.7 线程同步
import threading class XiaoAi(threading.Thread): def __init__(self,lock): # super() 可以用来获取当前类的父类 super().__init__(name='小爱') self.lock = lock def run(self): self.lock.acquire() print('{}: 在'.format(self.name)) self.lock.release() self.lock.acquire() print('{}: 你猜猜几点了'.format(self.name)) self.lock.release() class TianMao(threading.Thread): def __init__(self, lock): # super() 可以用来获取当前类的父类 super().__init__(name='天猫') self.lock = lock def run(self): self.lock.acquire() print('{}: 小爱同学'.format(self.name)) self.lock.release() self.lock.acquire() print('{}: 现在几点了?'.format(self.name)) self.lock.release() if __name__ == '__main__': # 定义一把锁 mutex = threading.RLock() xiaoai = XiaoAi(mutex) tianmao = TianMao(mutex) xiaoai.start() tianmao.start()
9.8 多线程爬取斗图啦
import os
import re
import threading
from queue import Queue
from urllib import request
import requests
headers = {
'User-Agent': "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50"}
class Prouducer(threading.Thread):
"""生产者img_url"""
def __init__(self, page_url_queue, img_queue, *args, **kwargs):
super(Prouducer, self).__init__(*args, **kwargs)
self.page_url_queue = page_url_queue
self.img_queue = img_queue
def get_response(self, url, headers=None):
try:
resp = requests.get(url, headers=headers)
if resp.status_code != 200:
raise RequestException("The request failed")
return resp.text
except Exception as e:
print(e)
def run(self):
while not self.page_url_queue.empty():
url = self.page_url_queue.get() # 从队列中获取页面的url
resp = self.get_response(url, headers)
params = re.compile(r'<a.*?class="col-xs-4 col-md-3">.*?<img.*?data-original="(.*?)".*?alt="(.*?)".*?</a>',re.S)
for img in re.findall(params, resp):
self.img_queue.put(img)
self.page_url_queue.task_done() # 让队列计数减一
class Consumer(threading.Thread):
"""消费者img_url"""
def __init__(self, img_queue, *args, **kwargs):
super(Consumer, self).__init__(*args, **kwargs)
self.img_queue = img_queue
def get_response(self, url, headers):
content = (request.urlopen(request.Request(url, headers=headers))).read()
print(url)
return content
def save(self, url, name, content):
name = "".join(re.split(r"[|\\/?:*<>!!]+", name))
suffix = os.path.splitext(url)[1]
file_name = "".join(['images/', name, suffix])
os.makedirs("images", exist_ok=True)
with open(file_name, "wb") as f:
f.write(content)
def run(self):
while not self.img_queue.empty():
url, name = self.img_queue.get()
print(url, name)
content = self.get_response(url, headers)
self.save(url, name, content)
self.img_queue.task_done()
class RequestException(Exception):
"""自定义异常"""
pass
def main():
page_url_queue = Queue()
img_queue = Queue()
for i in range(1, 2):
url = f"https://www.doutula.com/zz/list?page={i}"
page_url_queue.put(url)
for i in range(5):
t1 = Prouducer(page_url_queue, img_queue)
t1.start()
t1.join()
for i in range(5):
t2 = Consumer(img_queue)
t2.start()
t2.join() # 让主线程阻塞(等待子线程结束在结束)
if __name__ == '__main__':
main()
9.9 进程
-
1.导入进程包
-
2.导入进程包
import multiprocessing
-
3.Process进程类的说明
Process([group [, target [, name [, args [, kwarggl]]]]])- group :指定进程组,目前只能使用None
- target :执行的目标任务名
- name :进程名字
-
args :以元组方式给执行任务传参
-
kwargs :以字典方式给执行任务传参
-
Process创建的实例对象的常用方法
方法 功能 join() 等待⼦线程结束之后,主线程继续执⾏ terminate() 不管任务是否完成,立即终止子进程 start() 创建并启动线程 -
进程:正在执⾏的程序 ,进程之间不共享全局变量,主进程会等待所有子进程执行完才结束
-
进程之间不共享全局变量
import multiprocessing import time def task(): for i in range(10): print("任务执行中") time.sleep(0.2) if __name__ == '__main__': #创建子线程 sub_process=multiprocessing.Process(target=task) #把子线程设置成为守护主线程,以后主进程退出子进程直接销毁 # sub_process.daemon=True sub_process.start() #主进程延迟0.5秒 time.sleep(0.5) #让主进程退出之前先让子进程销毁 sub_process.terminate() print("over") ''' 结论:主进程会等待子进程执行完成以后程序再退出 解决方法:主进程退出子进程销毁 1.让子进程设置成为守护主进程,主进程退出子进程销毁,子进程会依赖主进程 2.让主进程退出之前先让子进程销毁 '''
-
主进程会等待所有的子进程执行结束再结束
import multiprocessing import time def task(): for i in range(10): print("任务执行中") time.sleep(0.2) if __name__ == '__main__': #创建子线程 sub_process=multiprocessing.Process(target=task) #把子线程设置成为守护主线程,以后主进程退出子进程直接销毁 # sub_process.daemon=True sub_process.start() #主进程延迟0.5秒 time.sleep(0.5) #让主进程退出之前先让子进程销毁 sub_process.terminate() print("over") ''' 结论:主进程会等待子进程执行完成以后程序再退出 解决方法:主进程退出子进程销毁 1.让子进程设置成为守护主进程,主进程退出子进程销毁,子进程会依赖主进程 2.让主进程退出之前先让子进程销毁 '''
9.9.1 多任务执行
-
并发和并行
- 并发:再一段时间内交替去执行任务
- 并行:对于多核cpu处理多任务,操作系统会给cpu每个内核安排一个执行软件,多个内核是真正的一起执行软件。
-
获取进程编号:
os.getid()
-
获取当前父进程编号:
os.getppid()
import os import multiprocessing import time def dance(): #获取子进程编号 dance_process_id = os.getpid() print('dance_process_id:', dance_process_id, multiprocessing.current_process) for i in range(3): print("跳舞中...") time.sleep(0.2) #根据进程编号杀死指定线程 os.kill(dance_process_id,9) def sing(): #获取父进程编号 sing_process_parent_id = os.getppid() print('sing_process_prent_id:', sing_process_parent_id, multiprocessing.current_process) for i in range(3): print("唱歌中...") time.sleep(0.2) #group:进程组,目前只使用None,一般不需要设置 #target:进程执行的目标任务 # name:进程名,如果不设置默认是Process-1 if __name__ == '__main__': dance_process = multiprocessing.Process(target=dance,name='dance_process') dance_process.start() #获取进程名 print('dance_process',dance_process) sing_process=multiprocessing.Process(target=sing) sing_process.start() # 获取当前进程编号(主线程) main_process_id = os.getpid() print('main_process_id:',main_process_id,multiprocessing.current_process)
-
执行带有参数的进程
import multiprocessing #显示信息的任务 def show_info(name,age): print(name,age) if __name__ == '__main__': # 创建子进程1 以元组方式传参 sub_process = multiprocessing.Process(target=show_info, args=('张三', 18)) sub_process.start() # 创建子进程2 以字典方式传参 sub_process = multiprocessing.Process(target=show_info, kwargs={"name":"李四","age":18}) sub_process.start()
-
程序:没有执⾏的代码,是⼀个静态的
-
线程和进程之间的对⽐
进程:能够完成多任务,⼀台电脑上可以同时运⾏多个QQ
线程:能够完成多任务,⼀个QQ中的多个聊天窗⼝根本区别:进程是操作系统资源分配的基本单位,⽽线程是任务调度和执⾏的基本单位
-
进程之间的通信
Queue-队列 先进先出
共享全局变量不适⽤于多进程编程
-
进程池之间的通信
当需要创建的⼦进程数量不多时,可以直接利⽤multiprocessing中的Process
动态⽣成多个进程,但是如果是上百甚⾄上千个⽬标,⼿动的去创建的进程的
⼯作量巨⼤,此时就可以⽤到multiprocessing模块提供的Pool⽅法
from multiprocessing import Pool
import os, time, random
def worker(msg):
t_start = time.time()
print('%s开始执行,进程号为%d' % (msg, os.getpid()))
time.sleep(random.random() * 2)
t_stop = time.time()
print(msg, "执行完成,耗时%0.2f" % (t_stop - t_start))
def demo():
pass
if __name__ == '__main__':
po = Pool(3) # 定义一个进程池
for i in range(0, 10):
po.apply_async(worker, (i,))
print("--start--")
po.close()
po.apply_async(demo)
po.join()
print("--end--")
import multiprocessing
def demo1(q):
print(1)
q.put('a')
def demo2(q):
print(2)
print(q.get())
if __name__ == '__main__':
# q = multiprocessing.Queue()
q = multiprocessing.Manager().Queue() # 进程池之间进程间的通信
po = multiprocessing.Pool(2)
po.apply_async(demo1,args=(q,))
po.apply_async(demo2,args=(q,))
po.close()
po.join()