Python多线程开发

1. 进程和线程

  • 线程,计算机中可以被CPU调度的最小单元
  • 进程,即运行中的程序,是计算机资源分配的最小单元(进程为线程提供资源)
  • 一个进程中可以有多个线程,同一个进程中的线程可以共享此进程中的资源
  • 进程和线程可以将串行的程序变为并发
  • 多进程的开销比多线程的开销大
  • GIL锁:
    • GIL,全局解释器锁,CPython解释器特有,让进程中同一个时刻只能有一个线程可以被CPU调用
    • 如果想利用计算机多核优势,让CPU同时处理任务,适合用多进程开发
    • 如果不利用计算机多核优势,适合多线程开发
    • 常见开发中,计算操作需要使用CPU多核优势,IO操作不需要利用CPU多核优势,故:
      • 计算密集型,用多进程
      • IO密集型,用多线程
  • 多进程多线程也可以结合使用

2. 线程常见方法

  • t.start(),当前线程准备就绪(等待CPU调度,具体时间由CPU来决定)
  • t.join(),等待当前线程的任务执行完毕后再向下继续执行
  • t.setDaemon(布尔值),守护线程(必须放在start之前)
    • t.setDaemon(True),设置为守护线程,主线程执行完毕后,子线程也自动关闭
    • t.setDaemon(False),设置为非守护线程,主线程等待子线程,子线程执行完毕后,主线程才结束。(不设置时自动默认子线程为非守护线程,等待子进程)
    import threading
    
    loop = 10000000
    number = 0
    
    def _add(count):
        global number
        for i in range(count):
            number += 1
    
    def _sub(count):
        global number
        for i in range(count):
            number -= 1
    
    t1 = threading.Thread(target=_add, args=(loop,))
    t2 = threading.Thread(target=_sub, args=(loop,))
    t1.start()  # t1线程等待CPU调度
    t1.join()  # t1线程执行完毕,才继续往后走
    t2.setDaemon(True)  # 设置t2为守护线程,主线程不必等待t2线程执行完毕
    t2.start()  # t2线程等待CPU调度
    
    print(number)
    
  • 线程名称的设置和获取
    import threading
    
    def task(arg):
    	# 获取当前执行此代码的线程
    	name = threading.current_thread().getName()
    	print(name)
    
    for i in range(10):
    	t = threading.Thread(target=task, args=(11,))
    	t.setName('线程-{}'.format(i))
    	t.start()
    
  • 自定义线程类,直接将线程需要做的事写到run方法中
    import threading
    
    class MyThread(threading.Thread):
    	def run(self):  # 将线程需要做的事写到run方法中
    		print('执行此线程', self._args)
    
    t = MyThread(args=(100,))
    t.start()
    

3. 线程安全

  • 一个进程中可以有多个线程,且线程共享进程中所有资源
  • 多个线程去操作同一个数据,可能会存在数据混乱
  • 可通过线程锁解决
  • 开发过程中,有些操作默认是线程安全的(内部集成了锁的机制),再使用中不需要再通过锁进行再处理(要多注意看一些开发文档中是否标明线程安全,例如:list类型的append、extend、pop、切片、sort,dict类型的update、keys等方法都是线程安全的)

4. 线程锁

程序中手动加锁,一般有两种:LockRLock

  • Lock,同步锁
    import threading
    
    num = 0
    lock_object = threading.Lock()
    
    def task():
    	print('开始')
    	lock_object.acquire()  # 加锁
    	global num
    	for i in range(10000):
    		num += 1
    	lock_object.release()  # 释放锁
    	print(num)
    
    for i in range(2):
    	t = threading.Thread(target=task)
    	t.start()
    
  • RLock,递归锁
    import threading
    
    num = 0
    lock_object = threading.RLock()
    
    def task():
        print("开始")
        lock_object.acquire()  # 第1个抵达的线程进入并上锁,其他线程就需要再此等待。
        global num
        for i in range(1000000):
            num += 1
        lock_object.release()  # 线程出去,并解开锁,其他线程就可以进入并执行了
        print(num)
    
    
    for i in range(2):
        t = threading.Thread(target=task)
        t.start()
    
  • RLock支持多次申请锁和多次释放锁;Lock不支持。示例:
    import threading
    import time
    
    lock_object = threading.RLock()
    
    
    def task():
        print("开始")
        lock_object.acquire()
        lock_object.acquire()
        print(123)
        lock_object.release()
        lock_object.release()
    
    for i in range(3):
        t = threading.Thread(target=task)
        t.start()
    
  • 应用场景示例:
    import threading
    lock = threading.RLock()
    
    # 程序员A开发了一个函数,函数可以被其他开发者调用,内部需要基于锁保证数据安全。
    def func():
    	with lock:
    		pass
        
    # 程序员C开发了一个函数,自己需要加锁,同时也需要调用func函数。
    def process():
        with lock:
    		print("其他功能")
            func() # ----------------> 此时就会出现多次锁的情况,只有RLock支持(Lock不支持)。
    		print("其他功能")
    

4. 死锁

  • 死锁,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象。

6. 线程池

  • Python3中官方才正式提供线程池
  • 线程不是开的越多越好,开的多了可能会导致系统的性能更低了,例如:如下的代码是不推荐在项目开发中编写
  • 不建议:无限制的创建线程
  • 线程池常用方法
    • pool.submit(任务名,参数) # 在线程池中提交一个任务,线程池中如果有空闲线程,则分配一个线程去执行,执行完毕后再将线程交还给线程池;如果没有空闲线程,则等待
    • pool.shutdown(布尔值) # 等待线程池中的任务执行完毕后,在继续向后执行
    • add_done_callback(方法名) # 任务执行完成,再执行一个方法。由子线程的主线程执行
      import time
      import random
      from concurrent.futures import ThreadPoolExecutor, Future
      
      
      def task(video_url):
          print("开始执行任务", video_url)
          time.sleep(2)
          return random.randint(0, 10)
      
      
      def done(response):
          print("任务执行后的返回值", response.result())
      
      
      # 创建线程池,最多维护10个线程。
      pool = ThreadPoolExecutor(10)
      
      url_list = ["www.xxxx-{}.com".format(i) for i in range(15)]
      
      for url in url_list:
          # 在线程池中提交一个任务,线程池中如果有空闲线程,则分配一个线程去执行,执行完毕后再将线程交还给线程池;如果没有空闲线程,则等待。
          future = pool.submit(task, url)
          future.add_done_callback(done) # 任务结束后,add_done_callback方法添加回调方法,由子线程的主线程执行
      poo.shutdown(True)  # 等待线程池任务执行完毕后,再继续执行
      print('继续向下执行')
          
      # 可以做分工,例如:task专门下载,done专门将下载的数据写入本地文件。
      
    • 最终统一获得线程池全部调用结果
      import time
      import random
      from concurrent.futures import ThreadPoolExecutor, Future
      
      def task(video_url):
      	print('执行任务', video_url)
      	time.sleep(2)
      	return random.randint(0, 10)
      
      pool = ThreadPoolExecutor(10)  # 创建线程池
      future_list = []
      url_list = ['www.xxx-{}.com'.format(i) for i in range(15)
      for url in url_list:
      	# 在线程池中提交一个任务,线程池中如果有空闲线程,则分配一个线程去执行,执行完毕再交还线程池。如果没有空闲线程,则等待
      	future = pool.submit(task, url)
      	future_list.append(future)
      
      pool.shutdown(True)
      # 获取结果
      for fu in future_list:
      	print(fu.result())
      

7.多线程中的单例模式

  • 单例模式,每次实例化类的对象,得到的都是最开始创建的那个对象,不再重复创建
  • 简单版本单例模式:
    class Singleton:
    	instance = None
    	
    	def __init__(self, name):
    		self.name = name
    	
    	def __new__(cls, *args, **kwargs):
    		if cls.instance:
    			return cls.instance
    		cls.instance = object.__new__(cls)
    		return cls.instance
    
    obj1 = Singleton('alex')
    obj2 = Singleton('SB')
    
    print(obj1, obj2)
    
  • 多线程执行单例模式,有BUG,有几率同时创建对象,加锁解决
    import threading
    import time
    
    class Singleton:
    	instance = None
    	lock = threading.RLock()
    
    	def __init__(self, name):
    		self.name = name
    	
    	def __new__(cls, *args, **kwargs):
    		if cls.instance:
    			return cls.instance
    		with cls.lock:
    			if cls.instance:
    				return cls.instance
    			time.sleep(0.1)
    			cls.instance = object.__new__(cls)
    		return cls.instance
    
    def task():
    	obj = Singleton('x')
    	print(obj)
    
    for i in range(10):
    	t = threading.Thread(target=task)
    	t.start()
    
    time.sleep(2)
    data = Singleton('asdf')
    print(data)
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值