Python线程

Python线程

1. 进程和线程

先来了解下进程和线程。

类比:

  • 一个工厂,至少有一个车间,一个车间中至少有一个工人,最终是工人在工作。

  • 一个程序,至少有一个进程,一个进程中至少有一个线程,最终是线程在工作。

    上述串行的代码示例就是一个程序,在使用python xx.py 运行时,内部就创建一个进程(主进程),在进程中创建了一个线程(主线程),由线程逐行运行代码。
    

进程和线程:

  • 线程,是计算机中可以被cpu调度的最小单元(真正在工作)。
  • 进程,是计算机资源分配的最小单元(进程为线程提供资源)。

一个进程中可以有多个线程,同一个进程中的线程可以共享此进程中的资源。

以前我们开发的程序中所有的行为都只能通过串行的形式运行,排队逐一执行,前面未完成,后面也无法继续。例如:

result = 0
for i in range(100000000):result += i
print(result)
import time
import requestsurl_list = [("抖音视频1.mp4", "https://v26-web.douyinvod.com/d715a7c5935857e3135400032fc7297e/66518623/video/tos/cn/tos-cn-ve-15/o0nQXgbOIVmul8IAehaBAsCoD5RAMgFg9EflJu/?a=6383&ch=5&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C3&cv=1&br=1753&bt=1753&cs=0&ds=4&ft=LjhJEL998xXdu4PmD0P58lZW_3iXwPklxVJE3QwCPCPD-Ipz&mime_type=video_mp4&qs=0&rc=N2VlNzo3NGg8Omg2NGRmNkBpMzxvMzk6ZmVocTMzNGkzM0A0YTBeYTQzXjExYDMyMmEuYSNpL15mcjQwNGdgLS1kLS9zcw%3D%3D&btag=c0000e00038000&cquery=100B_100H_100K_100a_101s&dy_q=1716614580&feature_id=46a7bb47b4fd1280f3d3825bf2b29388&l=2024052513225947A31C5B1D32D47EA5B6"),("抖音视频2.mp4", "https://v3-web.douyinvod.com/e710348d50895f395cbb045ec2b1f7a3/665184ca/video/tos/cn/tos-cn-ve-15c001-alinc2/oEtAtSQNceDyABDgfmLV9WgbcDILbAanEK50wG/?a=6383&ch=5&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C3&cv=1&br=955&bt=955&cs=0&ds=6&ft=LjhJEL998xXdu4PmD0P58lZW_3iXU4klxVJE3QwCPCPD-Ipz&mime_type=video_mp4&qs=12&rc=MzpnNDllOWRoM2QzZmhkO0BpM3R2ODg6ZjhnczMzNGkzM0AvNjZjMTE2X14xNjVjNV5iYSNtM2trcjRfYjJgLS1kLTBzcw%3D%3D&btag=c0000e00028000&cquery=100B_100H_100K_100a_101s&dy_q=1716614607&feature_id=46a7bb47b4fd1280f3d3825bf2b29388&l=202405251323263CE1D887DFEBCC8E1816"),
]print(time.time())
for file_name, url in url_list:res = requests.get(url)with open(file_name, mode='wb') as f:f.write(res.content)print(file_name, time.time())

通过 进程线程 都可以将 串行 的程序变为并发,对于上述示例来说就是同时下载两个视频,这样很短的时间内就可以下载完成。

1.1 多线程

基于多线程对上述串行示例进行优化:

  • 一个工厂,创建一个车间,这个车间中创建 3个工人,并行处理任务。
  • 一个程序,创建一个进程,这个进程中创建 3个线程,并行处理任务。
import time
import requests
import threading
"""
def func(a1,a2,a3):pass
t = threaing.Thread(target=函数名,args=参数)
例如:t = threaing.Thread(target=func,args=(11,22,33))
t.start()	# 创建好线程并让线程开始工作
"""url_list = [("抖音视频1.mp4", "https://v26-web.douyinvod.com/d715a7c5935857e3135400032fc7297e/66518623/video/tos/cn/tos-cn-ve-15/o0nQXgbOIVmul8IAehaBAsCoD5RAMgFg9EflJu/?a=6383&ch=5&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C3&cv=1&br=1753&bt=1753&cs=0&ds=4&ft=LjhJEL998xXdu4PmD0P58lZW_3iXwPklxVJE3QwCPCPD-Ipz&mime_type=video_mp4&qs=0&rc=N2VlNzo3NGg8Omg2NGRmNkBpMzxvMzk6ZmVocTMzNGkzM0A0YTBeYTQzXjExYDMyMmEuYSNpL15mcjQwNGdgLS1kLS9zcw%3D%3D&btag=c0000e00038000&cquery=100B_100H_100K_100a_101s&dy_q=1716614580&feature_id=46a7bb47b4fd1280f3d3825bf2b29388&l=2024052513225947A31C5B1D32D47EA5B6"),("抖音视频2.mp4", "https://v3-web.douyinvod.com/e710348d50895f395cbb045ec2b1f7a3/665184ca/video/tos/cn/tos-cn-ve-15c001-alinc2/oEtAtSQNceDyABDgfmLV9WgbcDILbAanEK50wG/?a=6383&ch=5&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C3&cv=1&br=955&bt=955&cs=0&ds=6&ft=LjhJEL998xXdu4PmD0P58lZW_3iXU4klxVJE3QwCPCPD-Ipz&mime_type=video_mp4&qs=12&rc=MzpnNDllOWRoM2QzZmhkO0BpM3R2ODg6ZjhnczMzNGkzM0AvNjZjMTE2X14xNjVjNV5iYSNtM2trcjRfYjJgLS1kLTBzcw%3D%3D&btag=c0000e00028000&cquery=100B_100H_100K_100a_101s&dy_q=1716614607&feature_id=46a7bb47b4fd1280f3d3825bf2b29388&l=202405251323263CE1D887DFEBCC8E1816"),
]def task(file_name, video_url):res = requests.get(video_url)with open(file_name, mode='wb') as f:f.write(res.content)print(time.time())for name, url in url_list:# 创建线程,让每个线程都去执行task函数(参数不同)t = threading.Thread(target=task, args=(name, url))t.start()

1.2 多进程

基于多进程对上述串行示例进行优化:

  • 一个工厂,创建 三个车间,每个车间 一个工人(共3人),并行处理任务。
  • 一个程序,创建 三个进程,每个进程 一个线程(共3人),并行处理任务。
import time
import requests
import multiprocessing# 进程创建之后,在进程中还会创建一个线程。
# t = multiprocessing.Process(target=函数名, args=参数)
# t.start()url_list = [("抖音视频1.mp4", "https://v26-web.douyinvod.com/d715a7c5935857e3135400032fc7297e/66518623/video/tos/cn/tos-cn-ve-15/o0nQXgbOIVmul8IAehaBAsCoD5RAMgFg9EflJu/?a=6383&ch=5&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C3&cv=1&br=1753&bt=1753&cs=0&ds=4&ft=LjhJEL998xXdu4PmD0P58lZW_3iXwPklxVJE3QwCPCPD-Ipz&mime_type=video_mp4&qs=0&rc=N2VlNzo3NGg8Omg2NGRmNkBpMzxvMzk6ZmVocTMzNGkzM0A0YTBeYTQzXjExYDMyMmEuYSNpL15mcjQwNGdgLS1kLS9zcw%3D%3D&btag=c0000e00038000&cquery=100B_100H_100K_100a_101s&dy_q=1716614580&feature_id=46a7bb47b4fd1280f3d3825bf2b29388&l=2024052513225947A31C5B1D32D47EA5B6"),("抖音视频2.mp4", "https://v3-web.douyinvod.com/e710348d50895f395cbb045ec2b1f7a3/665184ca/video/tos/cn/tos-cn-ve-15c001-alinc2/oEtAtSQNceDyABDgfmLV9WgbcDILbAanEK50wG/?a=6383&ch=5&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C3&cv=1&br=955&bt=955&cs=0&ds=6&ft=LjhJEL998xXdu4PmD0P58lZW_3iXU4klxVJE3QwCPCPD-Ipz&mime_type=video_mp4&qs=12&rc=MzpnNDllOWRoM2QzZmhkO0BpM3R2ODg6ZjhnczMzNGkzM0AvNjZjMTE2X14xNjVjNV5iYSNtM2trcjRfYjJgLS1kLTBzcw%3D%3D&btag=c0000e00028000&cquery=100B_100H_100K_100a_101s&dy_q=1716614607&feature_id=46a7bb47b4fd1280f3d3825bf2b29388&l=202405251323263CE1D887DFEBCC8E1816"),
]def task(file_name, video_url):res = requests.get(video_url)with open(file_name, mode='wb') as f:f.write(res.content)print(time.time())if __name__ == '__main__':print(time.time())for name, url in url_list:t = multiprocessing.Process(target=task, args=(name, url))t.start()

注:在创建进程的时候,linux内部基于fork实现,而python内部基于spawn实现。如果是基于spawn实现的进程创建,就必须将代码放到if __name__ == '__main__':运行,否则会报错

综上所述,我们会发现 多进程 的开销比 多线程 的开销大

1.3 GIL锁

GIL, 全局解释器锁(Global Interpreter Lock),是CPython解释器特有的,让一个进程中同一个时刻只能有一个线程可以被CPU调用。

如果程序想利用 计算机的多核优势,让CPU同时处理一些任务,适合用多进程开发(即使资源开销大)。

如果程序不利用 计算机的多核优势,适合用多线程开发。

常见的程序开发中,计算操作需要使用CPU多核优势,IO操作不需要利用CPU的多核优势,所以,就有这一句话:

  • 计算密集型,用多进程,例如:大量的数据计算【累加计算示例】。
  • IO密集型,用多线程,例如:文件读写、网络数据传输【下载抖音视频示例】。

累加计算示例(计算密集型):

  • 串行处理

    import timestart = time.time()result = 0
    for i in range(100000000):result += i
    print(result)end = time.time()print("耗时:", end - start) # 耗时: 5.035789489746094
    
  • 多进程处理

    import time
    import multiprocessingdef task(start, end, queue):result = 0for i in range(start, end):result += iqueue.put(result)if __name__ == '__main__':queue = multiprocessing.Queue()start_time = time.time()p1 = multiprocessing.Process(target=task, args=(0, 50000000, queue))p1.start()p2 = multiprocessing.Process(target=task, args=(50000000, 100000000, queue))p2.start()v1 = queue.get(block=True)v2 = queue.get(block=True)print(v1 + v2)end_time = time.time()print("耗时:", end_time - start_time)  # 耗时: 1.361469030380249
    

当然,在程序开发中 多线程 和 多进程 是可以结合使用,例如:创建2个进程(建议与CPU个数相同),每个进程中创建3个线程。

import multiprocessing
import threadingdef thread_task():passdef task(start, end):t1 = threading.Thread(target=thread_task)t1.start()t2 = threading.Thread(target=thread_task)t2.start()t3 = threading.Thread(target=thread_task)t3.start()if __name__ == '__main__':p1 = multiprocessing.Process(target=task, args=(0, 50000000))p1.start()p2 = multiprocessing.Process(target=task, args=(50000000, 100000000))p2.start()

2. 多线程开发

当代码写好后开始运行时,会有一个主线程从上至下运行代码,当运行到threading.Thread时会创建一个子线程

import threadingdef task(arg):pass# 创建一个Thread对象(线程),并封装线程被CPU调度时应该执行的任务和相关参数。
t = threading.Thread(target=task,args=('xxx',))
# 字线程准备就绪(等待CPU调度),代码继续向下执行。
t.start()print("继续执行...") # 主线程执行完所有代码,不结束(等所有子线程执行完毕,结束)

线程的常见方法:

  • t.start(),当前线程准备就绪(等待CPU调度,具体时间是由操作系统来决定)。

    import threadingloop = 10000000
    number = 0def _add(count):global numberfor i in range(count):number += 1t = threading.Thread(target=_add,args=(loop,))
    t.start()print(number)
    

    这段代码中的number自加操作是由子线程控制的,所以当最后打印number时,number的值时不确定的,主线程并不确定子线程是否执行完

  • t.join(),等待当前线程的任务执行完毕后再向下继续执行。

    import threadingnumber = 0def _add():global numberfor i in range(10000000):number += 1t = threading.Thread(target=_add)
    t.start()t.join() # 让主线程等待直至子线程完成print(number)	# 10000000
    
    import threadingnumber = 0def _add():global numberfor i in range(10000000):number += 1def _sub():global numberfor i in range(10000000):number -= 1# 创建两个线程
    t1 = threading.Thread(target=_add)
    t2 = threading.Thread(target=_sub)# 第一个线程准备完成,可以执行
    t1.start()
    t1.join()  # t1线程执行完毕,才继续往后走# 第二个线程准备完成,可以执行
    t2.start()
    t2.join()  # t2线程执行完毕,才继续往后走print(number)	# 0
    
    import threadingloop = 10000000
    number = 0def _add(count):global numberfor i in range(count):number += 1def _sub(count):global numberfor i in range(count):number -= 1t1 = threading.Thread(target=_add, args=(loop,))
    t2 = threading.Thread(target=_sub, args=(loop,))# 两个线程同时准备完成
    t1.start()
    t2.start()t1.join()  # t1线程执行完毕,才继续往后走
    t2.join()  # t2线程执行完毕,才继续往后走print(number)
    

    :这段代码在python3.10之前和python3.10之后有不同的返回值

    首先在 Python 字节码执行的时候 ,GIL 并不是随时能在任意位置中断切换线程。只有在主动检测中断的地方才可能发生线程切换。这个是大前提

    • python3.10之前:

    • 因为字节码中+=的操作是两步 opcode 操作,且 INPLACE_ADD 之后 GIL 会主动监测中断,导致虽然加了,但是没有重新赋值,就切换到了别的线程上

      这就导致当两个线程同时执行时,会使同一个cpu来回跳的执行这两个线程(执行一部分t1字线程之后,执行一部分t2子线程,再执行一部分t1字线程,如此重复)

    • python3.10之后:

      GIL 不再会主动检测中断,意味着正常情况下执行完+=之后线程不会被切换,而是正确执行了赋值给 num 的操作

    补充:在Python中我们可以通过dis模块来获取所定义函数执行流程的字节码

    import disdef add(a):a = a + 1return aprint(dis.dis(add))
    

    在这里插入图片描述

  • t.daemon = 布尔值 ,守护线程(必须放在start之前)

    • t.daemon = True,设置为守护线程,主线程执行完毕后,子线程也自动关闭。
    • t.daemon = False,设置为非守护线程,主线程等待子线程,子线程执行完毕后,主线程才结束。(默认)
    import threading
    import timedef task(arg):time.sleep(5)print('任务')t = threading.Thread(target=task, args=(11,))
    t.daemon = True # True/False
    t.start()print('END')
    
  • 线程名称的设置和获取

    import threadingdef task(arg):# 获取当前执行此代码的线程name = threading.current_thread().nameprint(name)for i in range(10):t = threading.Thread(target=task, args=(11,))t.name = 'wilson-{}'.format(i)t.start()
    
  • 自定义线程类,直接将线程需要做的事写到run方法中。

    import threadingclass MyThread(threading.Thread):def run(self):print('执行此线程', self._args)t = MyThread(args=(100,))
    t.start()
    
    import requests
    import threadingclass DouYinThread(threading.Thread):def run(self):file_name, video_url = self._argsres = requests.get(video_url)with open(file_name, mode='wb') as f:f.write(res.content)url_list = [("抖音视频1.mp4", "https://v26-web.douyinvod.com/d715a7c5935857e3135400032fc7297e/66518623/video/tos/cn/tos-cn-ve-15/o0nQXgbOIVmul8IAehaBAsCoD5RAMgFg9EflJu/?a=6383&ch=5&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C3&cv=1&br=1753&bt=1753&cs=0&ds=4&ft=LjhJEL998xXdu4PmD0P58lZW_3iXwPklxVJE3QwCPCPD-Ipz&mime_type=video_mp4&qs=0&rc=N2VlNzo3NGg8Omg2NGRmNkBpMzxvMzk6ZmVocTMzNGkzM0A0YTBeYTQzXjExYDMyMmEuYSNpL15mcjQwNGdgLS1kLS9zcw%3D%3D&btag=c0000e00038000&cquery=100B_100H_100K_100a_101s&dy_q=1716614580&feature_id=46a7bb47b4fd1280f3d3825bf2b29388&l=2024052513225947A31C5B1D32D47EA5B6"),("抖音视频2.mp4", "https://v3-web.douyinvod.com/e710348d50895f395cbb045ec2b1f7a3/665184ca/video/tos/cn/tos-cn-ve-15c001-alinc2/oEtAtSQNceDyABDgfmLV9WgbcDILbAanEK50wG/?a=6383&ch=5&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C3&cv=1&br=955&bt=955&cs=0&ds=6&ft=LjhJEL998xXdu4PmD0P58lZW_3iXU4klxVJE3QwCPCPD-Ipz&mime_type=video_mp4&qs=12&rc=MzpnNDllOWRoM2QzZmhkO0BpM3R2ODg6ZjhnczMzNGkzM0AvNjZjMTE2X14xNjVjNV5iYSNtM2trcjRfYjJgLS1kLTBzcw%3D%3D&btag=c0000e00028000&cquery=100B_100H_100K_100a_101s&dy_q=1716614607&feature_id=46a7bb47b4fd1280f3d3825bf2b29388&l=202405251323263CE1D887DFEBCC8E1816"),
    ]for item in url_list:t = DouYinThread(args=(item[0], item[1]))t.start()

3. 线程安全

一个进程中可以有多个线程,且线程共享所有进程中的资源。

多个线程同时去操作一个"东西",可能会存在数据混乱的情况

我们可以通过锁的机制来确保线程安全,防止数据混乱

  • 示例一:

    import threadinglock_object = threading.RLock()loop = 10000000
    number = 0def _add(count):lock_object.acquire() # 加锁global numberfor i in range(count):number += 1lock_object.release() # 释放锁def _sub(count):lock_object.acquire() # 申请锁(等待)global numberfor i in range(count):number -= 1lock_object.release() # 释放锁t1 = threading.Thread(target=_add, args=(loop,))
    t2 = threading.Thread(target=_sub, args=(loop,))
    t1.start()
    t2.start()t1.join()  # t1线程执行完毕,才继续往后走
    t2.join()  # t2线程执行完毕,才继续往后走print(number)	# 0
    
  • 示例2:

    import threadingnum = 0def task():global numfor i in range(1000000):num += 1print(num)for i in range(2):t = threading.Thread(target=task)t.start()
    
    import threadingnum = 0
    lock_object = threading.RLock()def task():print("开始")lock_object.acquire()  # 第1个抵达的线程进入并上锁,其他线程就需要再此等待。global numfor i in range(1000000):num += 1lock_object.release()  # 线程出去,并解开锁,其他线程就可以进入并执行了print(num)for i in range(2):t = threading.Thread(target=task)t.start()
    
    import threadingnum = 0
    lock_object = threading.RLock()def task():print("开始")with lock_object: # 基于上下文管理,内部自动执行 acquire 和 releaseglobal numfor i in range(1000000):num += 1print(num)for i in range(2):t = threading.Thread(target=task)t.start()
    

在开发的过程中要注意有些操作默认都是 线程安全的(内部集成了锁的机制),我们在使用的时无需再通过锁再处理,例如:

import threadingdata_list = []lock_object = threading.RLock()def task():print("开始")for i in range(1000000):data_list.append(i)print(len(data_list))for i in range(2):t = threading.Thread(target=task)t.start()

官方给出的线程安全的操作:

在这里插入图片描述

注意:要多注意看一些开发文档中是否标明线程安全。

4. 线程锁

在程序中如果想要自己手动加锁,一般有两种:Lock 和 RLock。

  • Lock,同步锁。

    import threadingnum = 0
    lock_object = threading.Lock()def task():print("开始")lock_object.acquire()  # 第1个抵达的线程进入并上锁,其他线程就需要再此等待。global numfor i in range(1000000):num += 1lock_object.release()  # 线程出去,并解开锁,其他线程就可以进入并执行了print(num)for i in range(2):t = threading.Thread(target=task)t.start()
    
  • RLock,递归锁。

    import threadingnum = 0
    lock_object = threading.RLock()def task():print("开始")lock_object.acquire()  # 第1个抵达的线程进入并上锁,其他线程就需要再此等待。global numfor i in range(1000000):num += 1lock_object.release()  # 线程出去,并解开锁,其他线程就可以进入并执行了print(num)for i in range(2):t = threading.Thread(target=task)t.start()
    

RLock支持多次申请锁和多次释放;Lock不支持。例如:

import threading
import timelock_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()

5.死锁

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

两种死锁情况:

import threadingnum = 0
lock_object = threading.Lock()def task():print("开始")lock_object.acquire()  # 第1个抵达的线程进入并上锁,其他线程就需要再此等待。lock_object.acquire()  # 第1个抵达的线程进入并上锁,其他线程就需要再此等待。global numfor i in range(1000000):num += 1lock_object.release()  # 线程出去,并解开锁,其他线程就可以进入并执行了lock_object.release()  # 线程出去,并解开锁,其他线程就可以进入并执行了print(num)for i in range(2):t = threading.Thread(target=task)t.start()
import threading
import time lock_1 = threading.Lock()
lock_2 = threading.Lock()def task1():lock_1.acquire()time.sleep(1)lock_2.acquire()print(11)lock_2.release()print(111)lock_1.release()print(1111)def task2():lock_2.acquire()time.sleep(1)lock_1.acquire()print(22)lock_1.release()print(222)lock_2.release()print(2222)t1 = threading.Thread(target=task1)
t1.start()t2 = threading.Thread(target=task2)
t2.start()

6.线程池

Python3中官方才正式提供线程池。

线程不是开的越多越好,开的多了可能会导致系统的性能更低了,例如:如下的代码是不推荐在项目开发中编写。

不建议:无限制的创建线程。

import threadingdef task(video_url):passurl_list = ["www.xxxx-{}.com".format(i) for i in range(30000)]for url in url_list:t = threading.Thread(target=task, args=(url,))t.start()# 这种每次都创建一个线程去操作,创建任务的太多,线程就会特别多,可能效率反倒降低了。

建议:使用线程池

示例1:

import time
from concurrent.futures import ThreadPoolExecutor# pool = ThreadPoolExecutor(100)	# 线程池中可以维护100个线程
# pool.submit(函数名,参数1,参数2,参数...)	# 将一个任务推到线程池中执行def task(video_url,num):print("开始执行任务", video_url)time.sleep(5)# 创建线程池,最多维护10个线程。
pool = ThreadPoolExecutor(10)url_list = ["www.xxxx-{}.com".format(i) for i in range(300)]for url in url_list:pool.submit(task, url,2)	# 主线程将300个任务同时交给线程池,但是由于线程池只能同时维护10个线程,所以线程池会去其中10个线程执行,其余的290个线程则等待,如果有线程执行完成,则填充print("END")

示例2:pool.shutdown(True)

主线程等待线程池的任务执行完毕。

import time
from concurrent.futures import ThreadPoolExecutordef task(video_url):print("开始执行任务", video_url)time.sleep(5)# 创建线程池,最多维护10个线程。
pool = ThreadPoolExecutor(10)url_list = ["www.xxxx-{}.com".format(i) for i in range(300)]
for url in url_list:pool.submit(task, url)print("执行中...")
pool.shutdown(True)  # 主线程等待,等待线程池中的任务执行完毕后,在继续执行
print('继续往下走')

示例3:add_done_callback(函数名)任务执行完之后再执行函数。

import time
import random
from concurrent.futures import ThreadPoolExecutor, Futuredef 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) # 当线程池中每一个线程任务执行完成之后,就会执行done函数

示例4:最终统一获取结果。

import time
import random
from concurrent.futures import ThreadPoolExecutor,Futuredef task(video_url):print("开始执行任务", video_url)time.sleep(2)return random.randint(0, 10)# 创建线程池,最多维护10个线程。
pool = ThreadPoolExecutor(10)future_list = []url_list = ["www.xxxx-{}.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 Foo:passobj1 = Foo()obj2 = Foo()
print(obj1,obj2)

以后开发会遇到单例模式,每次实例化类的对象时,都是最开始创建的那个对象,不再重复创建对象。

  • 简单的实现单例模式

    class Singleton:instance = Nonedef __init__(self, name):self.name = namedef __new__(cls, *args, **kwargs):# 返回空对象if cls.instance:return cls.instancecls.instance = object.__new__(cls)return cls.instanceobj1 = Singleton('wilson1')
    obj2 = Singleton('wilson2')print(obj1, obj2)
    
  • 多线程执行单例模式,有BUG

    10个线程刚开始同时来实例化对象的时候,都会停在__new__方法中time.sleep前,然后会同时实例化多个对象,导致单例模式失效

    如果没有加time.sleep,可能会因为第一个线程执行比较快,实例化好对象后已经将对象赋给instance变量,导致其余9个线程不会走到实例化,看似实现了单例模式,实则具有偶然性

    import threading
    import timeclass Singleton:instance = Nonedef __init__(self, name):self.name = namedef __new__(cls, *args, **kwargs):if cls.instance:return cls.instancetime.sleep(0.1)cls.instance = object.__new__(cls)return cls.instancedef task():obj = Singleton('x')print(obj)for i in range(10):t = threading.Thread(target=task)t.start()
    
  • 加锁解决BUG:在构造方法中加锁,会将同时到来的10个线程卡住,只有一个线程可以进入实例化,并将对象赋值给instance变量,然后其余线程执行时,由于instance对象有值,就不会走实例化对象,由此实现单例模式

    import threading
    import timeclass Singleton:instance = Nonelock = threading.RLock()def __init__(self, name):self.name = namedef __new__(cls, *args, **kwargs):with cls.lock:if cls.instance:return cls.instancetime.sleep(0.1)cls.instance = object.__new__(cls)return cls.instancedef task():obj = Singleton('x')print(obj)for i in range(10):t = threading.Thread(target=task)t.start()
  • 加判断,提升性能

    在申请锁的上方多加一层判断,这样当我们在代码后期再次需要实例化对象时,我们就可以免除申请锁的资源占用

    import threading
    import timeclass Singleton:instance = Nonelock = threading.RLock()def __init__(self, name):self.name = namedef __new__(cls, *args, **kwargs):if cls.instance:return cls.instancewith cls.lock:if cls.instance:return cls.instancetime.sleep(0.1)cls.instance = object.__new__(cls)return cls.instancedef task():obj = Singleton('x')print(obj)for i in range(10):t = threading.Thread(target=task)t.start()data = Singleton('wilson')
    print(data)
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/331434.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

不靠后端,前端也能搞定接口!

嘿,前端开发达人们!有个超酷的消息要告诉你们:MemFire Cloud来袭啦!这个神奇的东东让你们不用依赖后端小伙伴们,也能妥妥地搞定 API 接口。是不是觉得有点不可思议?但是事实就是这样,让我们一起…

141.字符串:重复的字符串(力扣)

题目描述 代码解决 class Solution { public:// 计算字符串s的next数组&#xff0c;用于KMP算法void getNext(int *next, const string& s){int j 0; // j是前缀的长度next[0] 0; // 初始化next数组&#xff0c;第一个字符的next值为0for (int i 1; i < s.size(); …

OpenHarmony 实战开发——一文总结ACE代码框架

一、前言 ACE_Engine框架是OpenAtom OpenHarmony&#xff08;简称“OpenHarmony”&#xff09;的UI开发框架&#xff0c;为开发者提供在进行应用UI开发时所必需的各种组件&#xff0c;以及定义这些组件的属性、样式、事件及方法&#xff0c;通过这些组件可以方便进行OpenHarmo…

AUTOMATIC1111/stable-diffusion-webui/stable-diffusion-webui-v1.9.3

配置环境介绍 目前平台集成了 Stable Diffusion WebUI 的官方镜像&#xff0c;该镜像中整合如下资源&#xff1a; GpuMall智算云 | 省钱、好用、弹性。租GPU就上GpuMall,面向AI开发者的GPU云平台 Stable Diffusion WebUI版本&#xff1a;v1.9.3 Python版本&#xff1a;3.10.…

插件:NGUI

一、版本 安装完毕后重启一下即可&#xff0c;否则可能创建的UI元素不生效 二、使用 Label文字 1、创建Canvs 2、只有根节点的这些脚本全部展开才能鼠标右键创建UI元素 3、选择字体 Sprite图片 1、选择图集 2、选择图集中的精灵 Panel容器 用来装UI的容器&#xff0c;一般UI…

从 0 实现一个文件搜索工具 (Java 项目)

背景 各文件系统下, 都有提供文件查找的功能, 但是一般而言搜索速度很慢 本项目仿照 everything 工具, 实现本地文件的快速搜索 实现功能 选择指定本地目录, 根据输入的信息, 进行搜索, 显示指定目录下的匹配文件信息文件夹包含中文时, 支持汉语拼音搜索 (全拼 / 首字母匹配…

Boss说,搞个深色B端系统。敢要就敢搞,宁被累死,不被吓死。

老规矩&#xff0c;先上文字说服&#xff08;洗脑&#xff09;自己&#xff0c;再附案例。 深色系B端系统是指在企业级应用中&#xff0c;使用深色主题的后台管理系统。这种设计风格主要以暗色调为主&#xff0c;如黑色、深灰色等&#xff0c;与传统的亮色主题相比&#xff0c…

Orangepi Zero2 linux系统摄像头设备文件名固定

文章目录 1. 寻找设备规则2. 使用udev规则修改挂载设备文件名称 问题&#xff1a; 在多次插拔usb摄像头或者在使用中不小心碰到或松了会导致设备文件名称变化&#xff0c;如从/dev/video1和/dev/video2变为/dev/video2和/dev/video3, 所以每次发生变化后都要充型修改代码或者重…

小红书无限加群脚本无需ROOT【使用简单无教程】

小红书无限加群脚本无需ROOT&#xff0c;包含了对应的小红书版本【使用简单无教程】 链接&#xff1a;https://pan.baidu.com/s/1HkLhahmHDFMKvqCC3Q3haA?pwd6hzf 提取码&#xff1a;6hzf

【Linux】TCP协议【中】{确认应答机制/超时重传机制/连接管理机制}

文章目录 1.确认应答机制2.超时重传机制&#xff1a;超时不一定是真超时了3.连接管理机制 1.确认应答机制 TCP协议中的确认应答机制是确保数据可靠传输的关键部分。以下是该机制的主要步骤和特点的详细解释&#xff1a; 数据分段与发送&#xff1a; 发送方将要发送的数据分成一…

Vue 3 组件基础与模板语法详解

title: Vue 3 组件基础与模板语法详解 date: 2024/5/24 16:31:13 updated: 2024/5/24 16:31:13 categories: 前端开发 tags: Vue3特性CompositionAPITeleportSuspenseVue3安装组件基础模板语法 Vue 3 简介 1. Vue 3 的新特性 Vue 3引入了许多新的特性&#xff0c;以提高框…

c++编程14——STL(3)list

欢迎来到博主的专栏&#xff1a;c编程 博主ID&#xff1a;代码小豪 文章目录 list成员类型构造、析构、与赋值iterator元素访问修改元素list的操作 list list的数据结构是一个链表&#xff0c;准确的说应该是一个双向链表。这是一个双向链表的节点结构&#xff1a; list的使用…

共享单车(八):数据库

实现后台数据库访问模块的框架&#xff0c;能够实现验证请求并响应&#xff08;支持数据库操作&#xff09;。 数据库设计 class SqlTabel //负责数据库表的创建 { public:SqlTabel(std::shared_ptr<MysqlConnection> sqlconn) :sqlconn_(sqlconn) {}bool CreateUserI…

超值分享50个DFM模型格式的素人直播资源,适用于DeepFaceLive的DFM合集

50直播模型&#xff1a;点击下载 作为直播达人&#xff0c;我在网上购买了大量直播用的模型资源&#xff0c;包含男模女模、明星脸、大众脸、网红脸及各种稀缺的路人素人模型。现在&#xff0c;我将这些宝贵的资源整理成合集分享给大家&#xff0c;需要的朋友们可以直接点击下…

电商项目之有趣的支付签名算法

文章目录 1 问题背景2 思路3 代码实现 1 问题背景 在发起支付的时候&#xff0c;一般都需要对发送的请求参数进行加密或者签名&#xff0c;下文简称这个过程为“签名”。行业内比较普遍的签发算法有&#xff1a; &#xff08;1&#xff09;按支付渠道给定的字段排序进行拼接&am…

amis 联动效果触发的几种方式

联动效果实现主要俩种方式: 1.表达式实现联动,基于组件内或数据链的变量变化的联动 比如&#xff1a; "source": "/amis/api/mock2/options/level2?name${name} " (必须是这种字符串拼接形式,在data数据映射中表达式不会触发联动) 所有初始化接口链…

Docker学习(3):镜像使用

当运行容器时&#xff0c;使用的镜像如果在本地中不存在&#xff0c;docker 就会自动从 docker 镜像仓库中下载&#xff0c;默认是从 Docker Hub 公共镜像源下载。 一、列出镜像列表 可以使用 docker images 来列出本地主机上的镜像。 各个选项说明&#xff1a; REPOSITORY&am…

UDP的报文结构和注意事项

UDP协议是在传输层的协议。 UDP无连接&#xff0c;不可靠传输&#xff0c;面向数据报&#xff0c;全双工。 UDP的报文结构 学习网络协议&#xff0c;最主要的就是报文格式。 对于UDP来说&#xff0c;应用层的数据到达&#xff0c;UDP之后&#xff0c;就会给应用层的数据报前面…

大工作量LUAD代谢重编程模型多组学(J Transl Med)

目录 1&#xff0c;单细胞早期、晚期和转移性 LUAD 的细胞动力学变化 2&#xff0c;细胞代谢重编程介导的LUAD驱动恶性转移的异质性 3&#xff0c;模型构建 S-MMR评分管线构建 4&#xff0c;S-MMR 模型的预后评估 5&#xff0c; 还开发了S-MMR 评分网络工具 6&#xff0c…

从零开始搭建Springboot项目脚手架4:保存操作日志

目的&#xff1a;通过AOP切面&#xff0c;统一记录接口的访问日志 1、加maven依赖 2、 增加日志类RequestLog 3、 配置AOP切面&#xff0c;把请求前的request、返回的response一起记录 package com.template.common.config;import cn.hutool.core.util.ArrayUtil; import cn.hu…