Python - 多线程与多进程

x33g5p2x  于2021-09-19 转载在 Python  
字(4.5k)|赞(0)|评价(0)|浏览(357)

多线程

程序默认都是单线程(这个默认线程又叫主线程,其他的线程都叫子线程)

Thread类的对象就是线程对象,程序需要多少个子线程就创建多少个Thread的对象

  1. import time
  2. from datetime import datetime
  3. from threading import Thread,current_thread

模拟电影下载线程:

  1. def download(movie_name:str):
  2. print(f'《{movie_name}》开始下载:{datetime.now()}')
  3. print(current_thread())
  4. time.sleep(2)
  5. print(f'《{movie_name}》下载结束:{datetime.now()}')

单线程下载三部电影

  1. download('天若有情')
  2. download('英雄本色')
  3. download('使徒行者')

多线程下载三部电影

创建子线程:

Thread(target = 函数,args = (元组))

函数 — 需要在子线程中调用的函数

元组 — 调用target对应的函数的时候传递的实参列表

1)、创建线程对象

  1. t1 = Thread(target=download,args=('天若有情',))
  2. t2 = Thread(target=download,args=('英雄本色',))
  3. t3 = Thread(target=download,args=('使徒行者',))

2)、启动线程

线程对象.start()

  1. t1.start()
  2. t2.start()
  3. t3.start()

线程类的子类对象

  1. from threading import Thread,current_thread
  2. import time
  3. from datetime import datetime
  1. class DownLoadThread(Thread):
  2. def __init__(self,movie_name):
  3. super().__init__() #调用父类__init__
  4. self.movie_name = movie_name
  5. def run(self) -> None: #返回值类型说明,none说明没有返回值
  6. print(f'《{self.movie_name}》开始下载:{datetime.now()}')
  7. print(current_thread())
  8. time.sleep(2)
  9. print(f'《{self.movie_name}》下载结束:{datetime.now()}')
  10. download = DownLoadThread('英雄本色')
  11. download1 = DownLoadThread('天若有情')
  12. download2 = DownLoadThread('使徒行者')
  13. # 通过start()调用run(),run()方法会在相应的子线程中调用
  14. download.start()
  15. download1.start()
  16. download2.start()

join操作

  1. import time
  2. from datetime import datetime
  3. from threading import Thread,current_thread
  4. from random import randint

join的用法:

线程对象.join() — 等待当前线程的任务结束后才执行后面的代码

  1. def download(movie_name:str):
  2. print(f'《{movie_name}》开始下载:{datetime.now()}')
  3. time.sleep(randint(3,5))
  4. print(f'《{movie_name}》下载结束:{datetime.now()}')
  1. t1 = Thread(target=download,args=('天若有情',))
  2. t2 = Thread(target=download,args=('英雄本色',))
  3. t3 = Thread(target=download,args=('使徒行者',))
  4. t1.start()
  5. t2.start()
  6. t3.start()
  7. #阻塞线程,待t1,t2,t3结束后再执行print()
  8. t1.join()
  9. t2.join()
  10. t3.join()
  11. print('所有电影下载完成!')

多进程

  1. import time
  2. from datetime import datetime
  3. from multiprocessing import Process,current_process
  4. from random import randint
  1. def download(movie_name:str):
  2. print(f'《{movie_name}》开始下载:{datetime.now()}')
  3. print(current_process())
  4. time.sleep(randint(3,5))
  5. print(f'《{movie_name}》下载结束:{datetime.now()}')

多进程必须写在 _ name_ = = ’ _ main_’

  1. if __name__ == '__main__':
  2. p1 = Process(target=download,args=('天若有情',))
  3. p2 = Process(target=download,args=('英雄本色',))
  4. p3 = Process(target=download,args=('使徒行者',))
  5. p1.start()
  6. p2.start()
  7. p3.start()

多线程数据返回问题

  1. from threading import Thread,current_thread

结论:在子线程中调用的函数如果有返回,这个返回值无法在任何地方获取

  1. data = []
  2. def download(name):
  3. print('*****')
  4. data.append(f'{name}') #收集数据
  5. def use_data():
  6. for x in data:
  7. print(f'使用{current_thread()}:{x}')
  8. thread1 = Thread(target=download,args=('使徒行者',))
  9. thread2 = Thread(target=download,args=('英雄本色',))
  10. thread3 = Thread(target=use_data)
  11. thread1.start()
  12. thread2.start()
  13. thread3.start()
  14. thread1.join()
  15. thread2.join()
  16. thread3.join()
  17. print(data)
线程间通信(线程间数据交流)
  1. from threading import Thread,Lock,RLock
  2. import time

同一个进程中的多个线程间的数据可以直接相互使用

  1. data = 100
  2. list1 = []
  3. def func1():
  4. time.sleep(2)
  5. global data
  6. print(data)
  7. data = 999
  8. list1.append(200)
  9. thread = Thread(target=func1)
  10. thread.start()
  11. # thread.join() #100
  12. # 999 [200]
  13. print(data,list1) #100 [] 100
  14. time.sleep(5)
线程间通信安全

加锁:一个数据一个锁(数据和锁需要对应)

1)、创建锁对象,保证一个数据一个锁

lock对象:锁对象.acquire() — 加锁;

​ 锁对象.release() — 释放锁

RLock对象: — 自动解锁

​ with 锁对象:

​ 操作数据的代码段

  1. lock = Lock()
  2. lock2 = RLock()
  3. def save_money(num:int):
  4. # 2、在使用需要锁的数据之前加锁
  5. lock.acquire()
  6. global balance
  7. b1 = balance
  8. time.sleep(2)
  9. balance = b1 + num
  10. # 3、数据使用完后释放锁(解锁)
  11. lock.release()
  12. print(f'当前账户余额为:{balance}')
  13. def draw_money(num:int):
  14. lock.acquire()
  15. global balance
  16. b1 = balance
  17. if(b1 > num):
  18. time.sleep(2)
  19. balance = b1 - num
  20. else:
  21. print('余额不足!')
  22. print(f'当前账户余额为:{balance}')
  23. lock.release()
  24. thread_s = Thread(target=save_money,args=(2000,))
  25. thread_d = Thread(target=draw_money,args=(3000,))
  26. thread_s.start()
  27. thread_d.start()

多线程图片爬取

  1. import requests
  2. from lxml import etree
  3. from threading import Thread
  4. def get_one_page(page: int):
  5. print(f'获取第{page}页数据')
  6. if page == 1:
  7. url = 'https://pic.netbian.com/index.html'
  8. else:
  9. url = f'https://pic.netbian.com/index_{page}.html'
  10. response = requests.get(url)
  11. response.encoding = 'gbk'
  12. html = etree.HTML(response.text)
  13. result = html.xpath('//div[@class="slist"]/ul/li/a/img/@src|//div[@class="slist"]/ul/li/a/span/img/@src')
  14. # return ['https://pic.netbian.com'+x for x in result]
  15. # 一页图片用一个线程来下载
  16. t = Thread(target=download_page_image, args=(['https://pic.netbian.com'+x for x in result],))
  17. t.start()
  18. # 一张图片一个线程
  19. # for url in ['https://pic.netbian.com'+x for x in result]:
  20. # t = Thread(target=download_image, args=(url,))
  21. # t.start()
  22. def get_all_data():
  23. for page in range(1, 11):
  24. t = Thread(target=get_one_page, args=(page, ))
  25. t.start()
  26. def download_page_image(urls: list):
  27. print(f'下载一页数据:{len(urls)}')
  28. for url in urls:
  29. download_image(url)
  30. def download_image(url: str):
  31. response = requests.get(url)
  32. f = open(f'files/{url.split("/")[-1]}', 'wb')
  33. f.write(response.content)
  34. # print('下载完成!')
  35. get_all_data()

相关文章