【python开发】并发编程(上)

并发编程(上)

  • 一、进程和线程
    • (一)多线程
    • (二)多进程
    • (三)GIL锁
  • 二、多线程开发
    • (一)t.start()
    • (二)t.join()
    • (三)t.setDaemon(布尔值)
    • (四)线程名称的设置和获取
    • (五)run方法
  • 三、线程安全
  • 四、线程锁
    • (一)Lock:同步锁
    • (二)RLock,递归锁
  • 五、死锁
  • 六、线程池
    • (一)示例一
    • (二)示例二
    • (三)示例三
    • (四)示例四
  • 七、单例模式(扩展)

一、进程和线程

类比:

  1. 一个工厂,至少需要一个车间,一个车间至少需要一个工人操作一台机器,最终是工人在工作。
  2. 一个程序,至少有一个进程,一个进程中至少有一个线程,最终是线程在工作。

线程是计算机中可以被cpu调度的最小单元;进程是计算机资源分配的最小单元(进程为线程提供资源)。一个进程中可以有多个线程,同一个进程中的线程可以共享次过程中的资源。一个进程中可以有多个线程,同一个进程中的线程可以共享此进程中的资源。

import time
import requestsurl_list = [('东北F4模仿秀.mp4', "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0300f570000bvbmace0gvch7lo53oog"),('卡特扣篮.mp4', "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f3e0000bv52fpn5t6p007e34qlg"),('罗斯mvp.mp4', "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f240000buuer5aa4tij4gv6ajqg")]start_time = time.time()
print(start_time)for file_name, url in url_list:res = requests.get(url)with open(file_name, mode='wb') as f:f.write(res.content)end_time = time.time()print(file_name, end_time-start_time)'''
1710295330.536654
东北F4模仿秀.mp4 0.9025721549987793
卡特扣篮.mp4 1.5163640975952148
罗斯mvp.mp4 2.1225790977478027
'''

从上述实验可以看出,在串行任务中下载三个视频耗费时间大致为2.12秒。

(一)多线程

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

  1. 一个工厂,创建一个车间,这个车间中安排3个工人,并行处理任务;
  2. 一个程序,创建一个进程,这个进程中创建3个线程,并行处理任务。

多线程threading,在引入threading模块后,引用Thread方法,Thread方法通常来说需要输入两个参数:target和args

import threadingdef thread_job():print('This is an added Thread, which is %s' % threading.current_thread())def main():# 添加一个线程added_thread = threading.Thread(target=thread_job)  # target表示要该线程要执行的任务# 启动线程added_thread.start()if __name__ == '__main__':main()

当我们用多线程方法下载三个抖音视频时:

import time
import requests
import threadingurl_list = [('东北F4模仿秀.mp4', "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0300f570000bvbmace0gvch7lo53oog"),('卡特扣篮.mp4', "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f3e0000bv52fpn5t6p007e34qlg"),('罗斯mvp.mp4', "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f240000buuer5aa4tij4gv6ajqg")]start_time = time.time()
print(start_time)def task(file_name, video_url):res = requests.get(video_url)with open(file_name, mode='wb') as f:f.write(res.content)end_time = time.time()print(file_name, end_time - start_time)for file_name, url in url_list:#创建线程,让每个线程都去执行task函数(参数不同)t = threading.Thread(target=task, args=(file_name, url))t.start()'''
1710296455.683249
罗斯mvp.mp4 0.8964550495147705
东北F4模仿秀.mp4 0.9174120426177979
卡特扣篮.mp4 0.9269850254058838
'''

在使用多线程的情况下,我们可以看出下载三个痘印视频最多需要0.9秒,比不使用多线程时少了将近一半时间。

(二)多进程

  1. 一个工厂,创建三个车间,每个车间一个工人(共三人),并行处理任务;
  2. 一个程序,创建三个进程,每个进程中创建一个线程(共三人),并行处理任务。
import time
import requests
import multiprocessingmultiprocessing.set_start_method('fork')#进程创建之,在进程中会创建一个线程
#t = multiprocessing.Process(target=函数名,args=(name, url))
#t.start()url_list = [('东北F4模仿秀.mp4', "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0300f570000bvbmace0gvch7lo53oog"),('卡特扣篮.mp4', "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f3e0000bv52fpn5t6p007e34qlg"),('罗斯mvp.mp4', "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f240000buuer5aa4tij4gv6ajqg")]def task(file_name, video_url):res = requests.get(video_url)with open(file_name, mode="wb") as f:f.write(res.content)end_time = time.time()print(end_time-start_time)if __name__ == '__main__':for name, url in url_list:start_time = time.time()t = multiprocessing.Process(target=task, args=(name, url))t.start()'''
0.6752231121063232
0.7185511589050293
0.7391998767852783
'''

根据以上实验可以看出:多进程耗费时间小于多线程。多线程最好是使用:if name == ‘main’:主方法,为什么不能跟多线程一样用for循环来实现(会报错),因为Linux系统只能支持fork,win系统可以只是spawn,mac支持fork和spwan(python3.8默认设置spawn)。

(三)GIL锁

GIL,全局解释锁(Global Interpreter Lock),是CPython解释器特有的,让一进程同一时刻只能由一个线程被CPU调用。
请添加图片描述
如果想利用计算机的多核优势,让CPU同时处理一些任务,适合用多进程开发(即使资源开销大)。如果不利用计算机的多核优势,则适合多线程开发。

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

累加计算串型计算(计算密集型):

import timeresult = 0
starttime = time.time()for i in range(10000000):result += 1#print(result)endtime = time.time()
print(endtime - starttime) #1.1689763069152832

在程序中创建两个进程时,通过两个累加计算结果相加来减少时间耗费。

import time
import multiprocessingresult = 0
starttime = time.time()for i in range(10000000):result += 1#print(result)endtime = time.time()
print(endtime - starttime) #1.1689763069152832def task(start, end, queue):result = 0for i in range(start, end):result += 1queue.put(result)if __name__ == '__main__':queue = multiprocessing.Queue()starttime_ = time.time()p1 = multiprocessing.Process(target=task, args=(0, 5000000, queue))p1.start()p2 = multiprocessing.Process(target=task, args=(5000000, 10000000, queue))p2.start()v1 = queue.get(block=True)v2 = queue.get(block=True)print(v1 + v2)endtime_ = time.time()print("耗时:", endtime_ - starttime_)
#耗时: 0.40788888931274414

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

二、多线程开发

主线程和子线程的关系:最常见的情况,主线程中开启了一个子线程,开启之后,主线程与子线程互不影响各自的生命周期,即主线程结束,子线程还可以继续执行;子线程结束,主线程也能继续执行。

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

编程中常见的方法:

(一)t.start()

当前线程准备就绪(等待CPU调度,具体时间由CPU来决定)

import threadingloop = 1000000
number = 0def _add(count):global numberfor i in range(count):number += 1t = threading.Thread(target=_add, args=(loop,))
t.start()print(number)
#第一次运行结果:45067
#第二次运行结果:46539
#第三次运行结果:60418

在上述例子中,正式因为start方法什么时候调度由cpu来决定,主线程和子线程个字进行,所以当主线程运行结束的时候,这个子线程运行到什么程度未可知,所以每次运行都会出现不同的数字。

(二)t.join()

等待当前线程的任务执行完毕后再向下继续执行

import threadingloop = 1000000
number = 0def _add(count):global numberfor i in range(count):number += 1t = threading.Thread(target=_add, args=(loop,))
t.start()t.join() #主线程等待中print(number)
#第一次运行结果:1000000
#第二次运行结果:1000000
#第三次运行结果:1000000

在上述的例子中可以知道,join方法中,主线程会等待子线程运行结束后才会执行,所以每次运行的结果都相同,数值均等于loop。

import threadingloop = 1000000
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() #t1子线程准备就绪,等待CPU调度
t1.join() #主线程等待中,t1子线程运行完毕才继续运行
t2.start()
t2.join()#主线程序等待,t2子线程运行完毕之后继续运行
print(number)
#0

如果调换start和join方法的顺序,结果将会不同。

import threadingloop = 1000000
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() #t1子线程准备就绪,等待CPU调度
t2.start()t1.join() #t1子线程运行完毕才继续运行
t2.join()#t2子线程运行完毕之后继续运行
print(number)
#第一次执行结果为:333015
#第二次执行结果为:-284747
#第三次执行结果为:186463

cpu会分区运行add和sub函数,而start()方法确定了cpu什么时候开始调度这两个函数无法确定,所以虽然在子线程t1结束之后再运行t2子线程,但是每次执行完主线程之后的数值也无法确定。

(三)t.setDaemon(布尔值)

守护线程(必须在start之前设置)

  1. t.setDaemon(True),设置为守护线程,主线程执行完毕之后,子线程也自动关闭
  2. t.setDaemon(False),设置为非守护线程,主程序等待子线程,子线程执行完毕后,主线程才结束。(默认)
def task(arg):time.sleep(5)print("任务")t = threading.Thread(target=task, args=(11,))
t.setDaemon(True)
t.start()print("End")#End

上述示例,设定了守护线程后,主线程结束之后,子线程也关闭了,所以只有主线程的结果输出。

import threading
import timedef task(arg):time.sleep(5)print("任务")t = threading.Thread(target=task, args=(11,))
t.setDaemon(False)
t.start()print("End")'''
End
任务
'''

(四)线程名称的设置和获取

import threadingdef task(arg):#获取当前执行此代码的线程name = threading.current_thread().getName()print(name)for i in range(10):t = threading.Thread(target=task, args=(11,))t.setName('happyeveryday{}'.format(i))t.start()'''
happyeveryday0
happyeveryday1
happyeveryday2
happyeveryday3
happyeveryday4
happyeveryday5
happyeveryday6
happyeveryday7
happyeveryday8
happyeveryday9
'''

(五)run方法

自定义线程类,直接将线程需要做的事写到run()方法中。

import threadingclass MyThread(threading.Thread): #继承了threading.Thread方法,def run(self):print('执行此线程', self._args)t = MyThread(args=(100, ))
t.start()
#执行此线程 (100,)

改写抖音下载的都线程代码

import time
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 = [('东北F4模仿秀.mp4', "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0300f570000bvbmace0gvch7lo53oog"),('卡特扣篮.mp4', "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f3e0000bv52fpn5t6p007e34qlg"),('罗斯mvp.mp4', "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f240000buuer5aa4tij4gv6ajqg")]for itme in url_list:t = DouYinThread(args=(itme[0], itme[1]))t.start()

三、线程安全

一个进程中可以有多个线程,且线程共享进程中的所有资源。多个线程同时去操作,可能会存在数据混乱的情况,例如:

import threadingloop = 1000000
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() #t1子线程准备就绪,等待CPU调度
t2.start()t1.join() #t1子线程运行完毕才继续运行
t2.join()#t2子线程运行完毕之后继续运行
print(number)
#第一次执行结果为:333015
#第二次执行结果为:-284747
#第三次执行结果为:186463

那么对于这种情况应该如何解决呢?用锁锁定程序:

import threading                                                  lock_object = threading.RLock() #建锁                               
loop = 1000000                                                    
number = 0                                                        def _add(count):                                                  lock_object.acquire() #申请锁                                    global number                                                 for i in range(count):                                        number += 1                                               lock_object.release() #释放锁                                    def _sub(count):                                                  lock_object.acquire() #申请锁(等待)                                global number                                                 for i in range(count):                                        number -= 1                                               lock_object.release()  #释放锁                                   t1 = threading.Thread(target=_add, args=(loop,))                  
t2 = threading.Thread(target=_sub, args=(loop,))                  t1.start() #t1子线程准备就绪,等待CPU调度                                     
t2.start()                                                        t1.join() #t1子线程运行完毕才继续运行                                         
t2.join()#t2子线程运行完毕之后继续运行                                         
print(number)                                                     
#第一次执行结果为:0                                                       
#第二次执行结果为:0                                                       
#第三次执行结果为:0                                                       

如果想要彻底解决数据混乱的问题,需要为两个线程构建同一个把锁,分别在线程内部编写申请锁和释放锁的代码。虽然cpu还是会分块执行两个线程,但是每次执行每个线程的结果将被锁死,以便于下次执行线程使用。

示例:

import threading                                                                       num = 0                                                                                
lock_object = threading.RLock()                                                        def task():                                                                            print("开始")                                                                        lock_object.acquire() #第一个抵达的线程进入并上锁,其他线程就需要再次等待                                   global num                                                                         for i in range(1000000):                                                           num += 1                                                                       lock_object.release()                                                              print(num)                                                                         for i in range(2):  #构造了循环函数,循环创建了两个线程,正常来说,两个线程都执行完,num=2000000                       t = threading.Thread(target=task)                                                  t.start()                                                                          '''                                                                                    
开始                                                                                     
开始                                                                                     
1000000                                                                                
2000000                                                                                
'''                                                                                    

在开发的过程中要注意有些操作是默认 “线程安全”的(内部结成了锁的机制),我们在使用的时候无需再通过锁处理,例如:append(x)、extend(x)、pop(x)、赋值=、update()。在操作不是默认线程安全的情况下可以需要加锁来避免数据混乱的情况。需要多注意看一些开发文档中是否标明线程安全。

四、线程锁

在程序中如果想自己手动加锁,可以加两种锁:Lock和Rlock。Lock和Rlock用法大致相同。

(一)Lock:同步锁

import threadingnum = 0
lock_object = threading.Lock()def task():print("开始")lock_object.acquire() #第一个抵达的线程进入并上锁,其他线程就需要再次等待global numfor i in range(1000000):num += 1lock_object.release()print(num)for i in range(2):  #构造了循环函数,循环创建了两个线程,正常来说,两个线程都执行完,num=2000000t = threading.Thread(target=task)t.start()

同步锁不支持在多次申请锁,否则会造成死锁的情况发生。下述例子是同步锁可以使用的:

def task():print("开始")lock_object.acquire() #第一个抵达的线程进入并上锁,其他线程就需要再次等待global numfor i in range(1000000):num += 1lock_object.release()lock_object.acquire() #第一个抵达的线程进入并上锁,其他线程就需要再次等待global numfor i in range(1000000):num += 1lock_object.release()print(num)

以下情况是不能使用两个锁的:

def task():print("开始")lock_object.acquire() #第一个抵达的线程进入并上锁,其他线程就需要再次等待lock_object.acquire() #会导致死锁,Lock是不能这么使用的,但Rlock可以global numfor i in range(1000000):num += 1lock_object.release()lock_object.release()print(num)

(二)RLock,递归锁

import threading                                                                       num = 0                                                                                
lock_object = threading.RLock()                                                        def task():                                                                            print("开始")                                                                        lock_object.acquire() #第一个抵达的线程进入并上锁,其他线程就需要再次等待                                   global num                                                                         for i in range(1000000):                                                           num += 1                                                                       lock_object.release()                                                              print(num)                                                                         for i in range(2):  #构造了循环函数,循环创建了两个线程,正常来说,两个线程都执行完,num=2000000                       t = threading.Thread(target=task)                                                  t.start()                                                                          '''                                                                                    
开始                                                                                     
开始                                                                                     
1000000                                                                                
2000000                                                                                
'''                                                                                    

Rlock支持多次申请和释放锁(lock不支持),例如:

import threadingnum = 0
lock_object = threading.RLock()def task():print("开始")lock_object.acquire() #第一个抵达的线程进入并上锁,其他线程就需要再次等待lock_object.acquire() #会导致死锁,Lock是不能这么使用的,但Rlock可以print(1234)lock_object.release()lock_object.release()for i in range(3):  #构造了循环函数,循环创建了两个线程,正常来说,两个线程都执行完,num=2000000t = threading.Thread(target=task)t.start()'''
开始
1234
开始
1234
开始
1234
'''

在什么情况下需要使用安全锁呢?程序员A和B都需要需要安全锁来保证自己开发的代码是数据安全的(都使用了安全锁),而且程序员B需要引用程序员A开发的代码,那么此时为了避免死锁问题,建议使用RLock。

五、死锁

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

死锁案例一:

import threadingnum = 0
lock_object = threading.Lock()def task():print("开始")lock_object.acquire() #第一个抵达的线程进入并上锁,其他线程就需要再次等待lock_object.acquire() #会导致死锁,进程会卡在这里,不会往下执行global numfor i in range(1000000):num += 1lock_object.release()lock_object.release()print(num)for i in range(2):  #构造了循环函数,循环创建了两个线程,正常来说,两个线程都执行完,num=2000000t = threading.Thread(target=task)t.start()

死锁案例二:

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

六、线程池

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

import threadingdef task(video_url):passurl_list = ["www.xxxxx-{}.com".format(i) for i in range(30000)]for url in url_list:t = threading.Thread(target=task, args=(url, ))t.start()

既然无限制得开线程会导致效率低下,因此可以使用线程池来解决这一问题。

(一)示例一

import time
from concurrent.futures import ThreadPoolExecutor#pool = ThreadPoolExecutor(100) 创建了100个线程def task(video_url, num):print("开始执行任务", video_url)time.sleep(5)#创建线程,最多维护10个线程pool = ThreadPoolExecutor(10)url_list = ["www.xxxxx-{}.com".format(i) for i in range(300)]for url in url_list:#在线程池中提交一个任务,线程池中如果有空闲线程,则分配一个线程去执行,执行完毕后再将线程交还给线程池;如果没有空闲线程则等待pool.submit(task, url, 2)print("end")

(二)示例二

等待线程池的任务执行完毕,主线程再继续执行,类似于线程里的join()方法。

import time
from concurrent.futures import ThreadPoolExecutor#pool = ThreadPoolExecutor(100) 创建了100个线程def task(video_url):print("开始执行任务", video_url)time.sleep(1)#创建线程,最多维护10个线程pool = ThreadPoolExecutor(10)url_list = ["www.xxxxx-{}.com".format(i) for i in range(200)]for url in url_list:#在线程池中提交一个任务,线程池中如果有空闲线程,则分配一个线程去执行,执行完毕后再将线程交还给线程池;如果没有空闲线程则等待pool.submit(task, url)print("执行中。。。。")
pool.shutdown(True) #等待线程池中的任务执行完毕后,再继续执行
print("end")

(三)示例三

任务执行完,再干点别的事情:add_done_callback

可以分工,通过task下载,用done专门将下载的数据写入本地文件。

import time
import random
from concurrent.futures import ThreadPoolExecutor,Future#pool = ThreadPoolExecutor(100) 创建了100个线程def task(video_url):print("开始执行任务", video_url)time.sleep(2)return random.randint(0, 10)def done(responce):print("任务执行后的返回值", responce.result)#创建线程,最多维护10个线程pool = ThreadPoolExecutor(10)url_list = ["www.xxxxx-{}.com".format(i) for i in range(200)]for url in url_list:#在线程池中提交一个任务,线程池中如果有空闲线程,则分配一个线程去执行,执行完毕后再将线程交还给线程池;如果没有空闲线程则等待future = pool.submit(task, url)future.add_done_callback(done) #是子主线程执行'''
开始执行任务 www.xxxxx-2.com
开始执行任务 www.xxxxx-3.com
开始执行任务 www.xxxxx-4.com开始执行任务 www.xxxxx-5.com
开始执行任务 www.xxxxx-6.com开始执行任务 www.xxxxx-7.com
开始执行任务 www.xxxxx-8.com
开始执行任务 www.xxxxx-9.com
任务执行后的返回值 <bound method Future.result of <Future at 0x7fa69912f340 state=finished returned int>>
任务执行后的返回值 <bound method Future.result of <Future at 0x7fa69912f700 state=finished returned int>>
任务执行后的返回值 <bound method Future.result of <Future at 0x7fa69912faf0 state=finished returned int>>
开始执行任务 任务执行后的返回值 任务执行后的返回值 任务执行后的返回值 <bound method Future.result of <Future at 0x7fa699122fa0 state=finished returned int>>
开始执行任务 开始执行任务 任务执行后的返回值 <bound method Future.result of <Future at 0x7fa6991117c0 state=finished returned int>>www.xxxxx-10.com
'''

(四)示例四

引入Future方法,直接输出结果。

import time
import random
from concurrent.futures import ThreadPoolExecutor,Futuredef task(video_url):print("开始执行任务",video_url)time.sleep(2)return random.randint(0, 10)pool = ThreadPoolExecutor(10)url_list = ["www.xxxxx-{}.com".format(i) for i in range(200)]future_list = []
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())'''
开始执行任务开始执行任务 www.xxxxx-195.com
开始执行任务 www.xxxxx-196.com
开始执行任务 www.xxxxx-197.com 开始执行任务 www.xxxxx-198.com开始执行任务 www.xxxxx-199.com
www.xxxxx-194.com
7
7
5
2
3
3
0
8
8
8
8
8
2
'''

七、单例模式(扩展)

单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。

主要有四种实现方式:

  1. 模块实现方式:

python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。

  1. 装饰器实现方式。
  2. 类方法实现。
  3. 基于__new__ 方法实现:、

我们知道,当我们实例化一个对象时,是先执行了类的__new__方法(我们没写时,默认调用object.new),实例化对象;然后再执行类的__init__方法,对这个对象进行初始化,所以我们可以基于这个,实现单例模式。

class Singleton:instance = Nonedef __init__(self, name):self.name = namedef __new__(cls, *args, **kwargs):#返回空对象if cls.instance:print("已经有了")return cls.instanceelse:print("还没有")cls.instance = object.__new__(cls)return cls.instanceobj1 = Singleton("alex")
obj2 = Singleton("nihao")print(obj1, obj2)
'''
还没有
已经有了
<__main__.Singleton object at 0x7fa5ce9417f0> <__main__.Singleton object at 0x7fa5ce9417f0>'''

从上述实验可以看出,通过__new__方法实现单例模后,实例化的对象调用了同一个内存。但是该方法不适用多线程,应为再多线程条件下,由于cpu分区执行,可能占据不同的内存保存对象。

class Singleton:instance = Nonedef __init__(self, name):self.name = namedef __new__(cls, *args, **kwargs):#返回空对象if cls.instance:#print("已经有了")return cls.instancetime.sleep(0.1)#print("还没有")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()'''
<__main__.Singleton object at 0x7fa11ba419d0><__main__.Singleton object at 0x7fa11d118fd0>
<__main__.Singleton object at 0x7fa11d0fefd0>
<__main__.Singleton object at 0x7fa11d1184c0><__main__.Singleton object at 0x7fa11b86caf0>
<__main__.Singleton object at 0x7fa11b86c850>
<__main__.Singleton object at 0x7fa11b86c730>
<__main__.Singleton object at 0x7fa11b86caf0>
<__main__.Singleton object at 0x7fa11b86c850>
<__main__.Singleton object at 0x7fa11b8868e0>
'''

针对这一个问题,同样可以用RLock来解决:

class Singleton:instance = Nonelock = threading.RLock()def __init__(self, name):self.name = namedef __new__(cls, *args, **kwargs):#返回空对象if cls.instance:return cls.instancecls.instance = object.__new__(cls)with cls.lock:if cls.instance:return cls.instancetime.sleep(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()'''
<__main__.Singleton object at 0x7fb7166d5910>
<__main__.Singleton object at 0x7fb7166d5910>
<__main__.Singleton object at 0x7fb7166d5910>
<__main__.Singleton object at 0x7fb7166d5910>
<__main__.Singleton object at 0x7fb7166d5910>
<__main__.Singleton object at 0x7fb7166d5910>
<__main__.Singleton object at 0x7fb7166d5910>
<__main__.Singleton object at 0x7fb7166d5910>
<__main__.Singleton object at 0x7fb7166d5910>
<__main__.Singleton object at 0x7fb7166d5910>
'''

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

Word粘贴时出现“运行时错误53,文件未找到:MathPage.WLL“的解决方案

在安装完MathType后&#xff0c;打开word复制粘贴时报错“运行时错误53,文件未找到&#xff1a;MathPage.WLL” 首先确定自己电脑的位数&#xff08;这里默认32位&#xff09; 右击MathType桌面图标&#xff0c;点击“打开文件所在位置”&#xff0c; 然后分别找到MathPage.W…

第一章:模型从硬盘文件到显示器像素的全过程分析

模型从硬盘文件到显示器像素的全流程分析 引言一、硬盘中的模型文件&#xff08;1&#xff09;分离式模型文件&#xff08;2&#xff09;模型主文件2.1 顶点几何与索引数据构成模型表面2.2 通过材质或贴图为模型表面着色2.3 通过骨骼动画驱动模型 二、CPU中的模型数据三、GPU中…

【第七章】深度学习思维模式

前言 尽管我们的神经网络表现出令人印象深刻的性能&#xff0c;但这种性能在某种程度上是神秘的。网络中的权重和偏差是自动发现的。这意味着我们并没有立即能够解释网络如何实现其功能。我们能否找到某种方式来理解我们的网络是如何对手写数字进行分类的原则&#xff1f;而且…

高中数学:复杂函数图像的草图画法(较难)

一、三大基本函数图像 1、指数函数图像 2、对数函数图像 3、幂函数图像 二、案例 口诀&#xff1a;上加下减&#xff0c;左加右减 1、指数函数图像变化规律 2、带绝对值的函数图像 情况1&#xff1a;绝对值套住整个函数表达式 画法原则&#xff1a;x轴下方的图像&#xff0…

【计算机组成原理】处理机管理

目录 一、处理机分类 1. MPU 2. MCU 3. DSP 二、CPU的功能 1. 指令控制 2. 操作控制 3. 时间控制 4. 数据加工 三、CPU的组成 &#xff08;一&#xff09;控制器 1. 控制器的功能 &#xff08;1&#xff09;控制指令执行的顺序 ① 程序中规定的转移类型指令 ② …

Nacos与Eureka的使用与区别

Nacos与Eureka的使用与区别 单体架构&#xff1a;优点缺点 分布式架构需要考虑的问题&#xff1a;微服务企业需求 认识SpringCloud服务的拆分与远程调用微服务调用方式 Eureka提供者和消费者架构搭建Eureka服务注册服务发现 Ribbon负载均衡饥饿加载总结 Nacos注册中心Nacos安装…

前端React篇之React的生命周期有哪些?

目录 React的生命周期有哪些&#xff1f;挂载阶段&#xff08;Mounting&#xff09;更新阶段&#xff08;Updating&#xff09;卸载阶段&#xff08;Unmounting&#xff09;错误处理阶段&#xff08;Error Handling&#xff09; React常见的生命周期React主要生命周期 React的生…

【List集合】List接口源码解读一(ArrayList)

目录 前言 1. List接口的基本信息 2. ArrayList 2.1.ArrayList 的基本信息 2.2. ArrayList 的构造方法 2.2.1 ArrayList 的构造方法一 2.2.2 ArrayList 的构造方法二 2.2.3 ArrayList 的构造方法三 2.3 ArrayList 的扩容方式 总结 前言 Java 语言由于其跨平台、社区良…

微信小程序开发系列(三十四)·自定义组件的创建、注册以及使用(数据和方法事件的使用)

目录 1. 分类和简介 2. 公共组件 2.1 创建 2.2 注册 2.3 使用 3. 页面组件 3.1 创建 3.2 注册 3.3 使用 4. 组件的数据和方法的使用 4.1 组件数据的修改 4.2 方法事件的使用 1. 分类和简介 小程序目前已经支持组件化开发&#xff0c;可以将页面中的功能…

Three 材质纹理 (总结三)

THREE.MeshLambertMaterial&#xff08;网格 Lambert 材质&#xff09; 该材质使用基于非物理的Lambertian模型来计算反射率。可以用来创建暗淡的并不光亮的表面&#xff0c;该材质非常易用&#xff0c;而且会与场景中的光源产生反应。 MeshLambertMaterial属性 # .color : …

24年上半年英语四六级报名时间25地汇总一览表

目前有25地公布了报名时间&#xff0c;大多集中在3月中下旬&#xff0c;具体时间以学校通知为准。 7个省份官宣 • 贵州(官方)&#xff1a;3月18日12:00-3月29日16:00 • 黑龙江(官方)&#xff1a;3月18日14:00-3月29日17:00 • 江西(官方)&#xff1a;3月19日6:00-3月25日17:0…

05.BOM对象

一、js组成 JavaScript的组成 ECMAScript: 规定了js基础语法核心知识。比如&#xff1a;变量、分支语句、循环语句、对象等等 Web APIs : DOM 文档对象模型&#xff0c; 定义了一套操作HTML文档的APIBOM 浏览器对象模型&#xff0c;定义了一套操作浏览器窗口的API 二、windo…

deepseek-coder模型量化

1 简介 DeepSeek-Coder在多种编程语言和各种基准测试中取得了开源代码模型中最先进的性能。 为尝试在开发板进行部署&#xff0c;首先利用llama.cpp对其进行量化。 2 llama.cpp安装 git clone之后进入文件夹make即可&#xff0c;再将依赖补全pip install -r requirements.tx…

【Miniconda】基于conda避免运行多个PyTorch项目时发生版本冲突

【Miniconda】基于conda避免运行多个PyTorch项目时发生版本冲突 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448; 希望得到…

原创 《vtk9 book》 官方web版 第四章 - 可视化管线(1 / 2)

在前一章中&#xff0c;我们使用简单的数学模型创建了图形图像&#xff0c;用于光照、视图和几何。光照模型包括环境光、漫反射和镜面效果。视图包括透视和投影的效果。几何被定义为一组静态的图形原语&#xff0c;如点和多边形。为了描述可视化过程&#xff0c;我们需要扩展我…

读算法的陷阱:超级平台、算法垄断与场景欺骗笔记12_移动平台(上)

1. 广告 1.1. 广告收入的来源 1.1.1. 向客户推荐广告投放网址 1.1.2. 提供有效提高产品广告点击率的咨询服务 1.1.3. 从合作伙伴的广告收入中捞上一笔 1.2. 对于广告主来讲&#xff0c;他们无意于与各家网站逐一谈判 1.2.1. 这种方式一是成本过高&#xff0c;二是费时费力…

Github 2024-03-17 php开源项目日报 Top9

根据Github Trendings的统计,今日(2024-03-17统计)共有9个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量PHP项目9Blade项目2Laravel:表达力和优雅的 Web 应用程序框架 创建周期:4631 天开发语言:PHP, BladeStar数量:75969 个Fork数量:24281 次关…

Delphi7应用教程学习1.3【练习题目】:文本及悬停文字的显示

这个例子主要用到了btn的Hint 属性&#xff0c;Hint是提示的意思。 还有Delphi7还是很好用的&#xff0c;改变了的属性是粗体&#xff0c;默认没有改变的属性为细体。

力扣新思路题:字符串轮转

非常简单的思路&#xff1a;将两个字符串s1接起来&#xff0c;并判断s2字符串是否是加长版s1字符串的子串 bool isFlipedString(char* s1, char* s2){if (strlen(s1) ! strlen(s2)) {return false;}int len strlen(s1);int i 0;char* arr (char*)malloc(sizeof(char) * len…

深入理解RAG:检索与生成的融合

原文地址&#xff1a;https://dev.to/portkey/understanding-rag-a-deeper-dive-into-the-fusion-of-retrieval-and-generation-1l4b 深入理解RAG:检索与生成的融合 检索增强生成(RAG)模型代表了检索系统和生成模型两大不同但互补组件完美结合的杰作。通过无缝集成相关信息检…