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

news2024/11/24 4:49:15

并发编程(上)

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

一、进程和线程

类比:

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

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

import time
import requests


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")]

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 threading


def 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 threading


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")]


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 multiprocessing


multiprocessing.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 time

result = 0
starttime = time.time()

for i in range(10000000):
    result += 1
    #print(result)


endtime = time.time()
print(endtime - starttime) #1.1689763069152832

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

import time
import multiprocessing

result = 0
starttime = time.time()

for i in range(10000000):
    result += 1
    #print(result)


endtime = time.time()
print(endtime - starttime) #1.1689763069152832


def task(start, end, queue):
    result = 0
    for i in range(start, end):
        result += 1
    queue.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 threading


def task(arg):
    pass


#创建一个Thread对象(线程),并封装线程被CPU调度时应该执行的任务和相关参数。
t = threading.Thread(target=task, args=('xxx'))
#线程准备就绪(等待CPU调度),代码继续向下执行
t.start()

print("继续执行。。。") #主线程执行完所有代码,不结束(等待子线程)

编程中常见的方法:

(一)t.start()

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

import threading

loop = 1000000
number = 0

def _add(count):
    global number
    for i in range(count):
        number += 1

t = threading.Thread(target=_add, args=(loop,))
t.start()

print(number)
#第一次运行结果:45067
#第二次运行结果:46539
#第三次运行结果:60418

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

(二)t.join()

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

import threading

loop = 1000000
number = 0

def _add(count):
    global number
    for i in range(count):
        number += 1

t = threading.Thread(target=_add, args=(loop,))
t.start()

t.join() #主线程等待中

print(number)
#第一次运行结果:1000000
#第二次运行结果:1000000
#第三次运行结果:1000000

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

import threading

loop = 1000000
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.start()
t2.join()#主线程序等待,t2子线程运行完毕之后继续运行
print(number)
#0

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

import threading

loop = 1000000
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调度
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 time


def task(arg):
    time.sleep(5)
    print("任务")


t = threading.Thread(target=task, args=(11,))
t.setDaemon(False)
t.start()

print("End")

'''
End
任务
'''

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

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('happyeveryday{}'.format(i))
    t.start()

'''
happyeveryday0
happyeveryday1
happyeveryday2
happyeveryday3
happyeveryday4
happyeveryday5
happyeveryday6
happyeveryday7
happyeveryday8
happyeveryday9
'''
    

(五)run方法

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

import threading


class MyThread(threading.Thread): #继承了threading.Thread方法,

    def run(self):
        print('执行此线程', self._args)

        
t = MyThread(args=(100, ))
t.start()
#执行此线程 (100,)

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

import time
import requests
import threading

class DouYinThread(threading.Thread):
    def run(self):
        file_name, video_url = self._args
        res = 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 threading

loop = 1000000
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调度
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 threading

num = 0
lock_object = threading.Lock()


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()

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

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

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

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

num = 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=2000000
    t = threading.Thread(target=task)
    t.start()
    

'''
开始
1234
开始
1234
开始
1234
'''

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

五、死锁

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

死锁案例一:

import threading

num = 0
lock_object = threading.Lock()


def task():
    print("开始")
    lock_object.acquire() #第一个抵达的线程进入并上锁,其他线程就需要再次等待
    lock_object.acquire() #会导致死锁,进程会卡在这里,不会往下执行
    global num
    for i in range(1000000):
        num += 1
    lock_object.release()
    lock_object.release()
    print(num)


for i in range(2):  #构造了循环函数,循环创建了两个线程,正常来说,两个线程都执行完,num=2000000
    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()

六、线程池

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

import threading


def task(video_url):
    pass


url_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,Future


def 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 = None

    def __init__(self, name):
        self.name = name

    def __new__(cls, *args, **kwargs):
        #返回空对象

        if cls.instance:
            print("已经有了")
            return cls.instance
        else:
            print("还没有")
            cls.instance = object.__new__(cls)

        return cls.instance


obj1 = Singleton("alex")
obj2 = Singleton("nihao")

print(obj1, obj2)
'''
还没有
已经有了
<__main__.Singleton object at 0x7fa5ce9417f0> <__main__.Singleton object at 0x7fa5ce9417f0>

'''

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

class Singleton:

    instance = None

    def __init__(self, name):
        self.name = name

    def __new__(cls, *args, **kwargs):
        #返回空对象

        if cls.instance:
            #print("已经有了")
            return cls.instance
        time.sleep(0.1)
        #print("还没有")
        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()


'''
<__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 = None
    lock = threading.RLock()

    def __init__(self, name):
        self.name = name

    def __new__(cls, *args, **kwargs):
        #返回空对象

        if cls.instance:
            return cls.instance
        cls.instance = object.__new__(cls)

        with cls.lock:
            if cls.instance:
                return cls.instance
            time.sleep(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()


'''
<__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://www.coloradmin.cn/o/1522632.html

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

相关文章

基于ESTAR指数平滑转换自回归模型的CPI数据统计分析matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1 ESTAR模型概述 4.2 WNL值&#xff0c;P值&#xff0c; Q值&#xff0c;12阶ARCH值 4.3ADF检验 5.完整程序 1.程序功能描述 基于ESTAR指数平滑转换自回归模型的CPI数据统计分析matlab仿…

【Hadoop大数据技术】——MapReduce经典案例实战(倒排索引、数据去重、TopN)

&#x1f4d6; 前言&#xff1a;MapReduce是一种分布式并行编程模型&#xff0c;是Hadoop核心子项目之一。实验前需确保搭建好Hadoop 3.3.5环境、安装好Eclipse IDE &#x1f50e; 【Hadoop大数据技术】——Hadoop概述与搭建环境&#xff08;学习笔记&#xff09; 目录 &#…

基于springboot+mysql+Shiro实现的宠物医院管理系统

1.项目介绍 系统主要为用户提供了管理员权限的用户&#xff0c;实现了前台查看客户信息、在线添加预约等&#xff1b;后台管理医生坐诊信息、管理就诊信息、修改密码&#xff0c;管理公告、管理宠物分类、管理就诊、管理用户、修改密码等。在设计方面&#xff0c;本系统采用MV…

Echo框架:高性能的Golang Web框架

Echo框架&#xff1a;高性能的Golang Web框架 在Golang的Web开发领域&#xff0c;选择一个适合的框架是构建高性能和可扩展应用程序的关键。Echo是一个备受推崇的Golang Web框架&#xff0c;以其简洁高效和强大功能而广受欢迎。本文将介绍Echo框架的基本特点、使用方式及其优势…

计算机网络——物理层(数据交换方式)

计算机网络——数据交换方式 提高数据交换方式的必要性电路交换电路交换原理电路交换的阶段建立阶段通信阶段和连接拆除阶段 电路交换的优缺点报文交换什么是报文报文交换的阶段报文交换的优缺点 分组交换分组交换的阶段分组交换的优缺点 数据交换方式的选择数据报方式数据报方…

【二】【单片机】有关独立按键的实验

自定义延时函数Delay 分别用Delay.c文件存储Delay函数。用Delay.h声明Delay函数。每次将这两个文件复制到工程中&#xff0c;直接使用。 //Delay.c void Delay(unsigned int xms) //11.0592MHz {while(xms--){unsigned char i, j;i 2;j 199;do{while (--j);}…

[自研开源] MyData 数据集成之数据过滤 v0.7.2

开源地址&#xff1a;gitee | github 详细介绍&#xff1a;MyData 基于 Web API 的数据集成平台 部署文档&#xff1a;用 Docker 部署 MyData 使用手册&#xff1a;MyData 使用手册 试用体验&#xff1a;https://demo.mydata.work 交流Q群&#xff1a;430089673 概述 本篇基于…

Ubuntu 虚拟机安装

最小化安装后常用工具 sudo apt-get install vim# ifconfig apt install net-tools # nload apt install nload # 很多都要用到 apt install build-essential # 开发相关 apt install gcc gapt install iproute2 ntpdate tcpdump telnet traceroute \ nfs-kernel-server nfs…

Java项目:57 ssm011线上旅行信息管理系统ssm+vue

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 本线上旅行信息管理系统&#xff0c;主要实现了用户功能模块和管理员功能模块两大部分 用户可查看旅行相关信息&#xff0c;注册登录后还可实…

第十二届蓝桥杯EDA省赛真题分析

前言&#xff1a; 第十二届蓝桥杯EDA比赛用的是AD软件&#xff0c;从第十四届起都是使用嘉立创EDA专业版&#xff0c;所以在这里我用嘉立创EDA专业版实现题目要求。 一、省赛第一套真题题目 主观题整套题目如下&#xff1a; 试题一&#xff1a;库文件设计&#xff08;5分&am…

Django 解决新建表删除后无法重新创建等问题

Django 解决新建表删除后无法重新创建等问题 问题发生描述处理办法首先删除了app对应目录migrations下除 __init__.py以外的所有文件:然后&#xff0c;删除migrations中关于你的app的同步数据数据库记录最后&#xff0c;重新执行迁移插入 问题发生描述 Django创建的表&#xf…

【计算机视觉】二、图像形成——实验:2D变换编辑(Pygame)

文章目录 一、向量和矩阵的基本运算二、几何基元和变换1、几何基元(Geometric Primitives)2、几何变换(Geometric Transformations)2D变换编辑器0. 程序简介环境说明程序流程 1. 各种变换平移变换旋转变换等比缩放变换缩放变换镜像变换剪切变换 2. 按钮按钮类创建按钮 3. Pygam…

前端vue-Taro框架中使用插件 ---pinyin 将城市树形分类

1.需求 当我做一个获取城市的功能的时候 我发向后端返回的数据 和我想i选要的相差太多 这样的在手机端可以滑动 并且 快捷选中的城市列表 目前的数据是这样的&#xff0c;就是一个城市数组 目前这样的数组 我要想显示我的页面实现功能是不行的 需要是树形结够 所以我前端…

CI/CD实战-git工具使用 1

版本控制系统 本地版本控制系统 集中化的版本控制系统 分布式版本控制系统 git官网文档&#xff1a;https://git-scm.com/book/zh/v2 Git 有三种状态&#xff1a;已提交&#xff08;committed&#xff09;、已修改&#xff08;modified&#xff09; 和 已暂存&#xff08;sta…

【CTF web1】

CTF web 一、CTF web -PHP弱类型1、是否相等&#xff1f;2、转换规则: 二、CTF web -md5绕过1、若类型比较绕过2、null绕过3、碰撞绕过 三、习题 一、CTF web -PHP弱类型 1、是否相等&#xff1f; &#xff1a;在进行比较的时候&#xff0c;会先判断两种字符串的类型是否相等&…

EVENG环境安装及测试 1

文章目录 下载eve镜像导入镜像访问测试导入自定义镜像 下载eve镜像 下载地址 链接&#xff1a;https://pan.baidu.com/s/1NqGE34oE5qZ6TCugMymPDg 提取码&#xff1a;f4m1 导入镜像 安装vmware 虚拟机&#xff0c;文件->打开 选中上述镜像 输入虚拟机的名称和保存 路径&a…

接口幂等性问题和常见解决方案

接口幂等性问题和常见解决方案 1.什么是接口幂等性问题1.1 会产生接口幂等性的问题1.2 解决思路 2.接口幂等性的解决方案2.1 唯一索引解决方案2.2 乐观锁解决方案2.3 分布式锁解决方案2.4 Token解决方案(最优方案) 1.什么是接口幂等性问题 幂等性: 用户同一操作发起的一次或多…

无人机助力智慧农田除草新模式,基于YOLOv5全系列【n/s/m/l/x】参数模型开发构建无人机航拍场景下的农田杂草检测识别系统

科技发展到今天&#xff0c;无人机喷洒药物已经不是一件新鲜事情了&#xff0c;在很多高危的工作领域中&#xff0c;比如高空电力设备除冰&#xff0c;电力设备部件传送更换等等&#xff0c;无人机都可以扮演非常出色的作用&#xff0c;前面回到老家一段时间&#xff0c;最近正…

Linux系统——Session ID(负载均衡如何保持会话)

目录 一、实验环境搭建 二、部署Nginx代理服务器配置 三、部署后端真是服务器Tomcat配置 四、配置Tomcat的Session ID会话保持 五、测试 此次实验是Tomcat后端服务器如何做Session ID会话保持 一、实验环境搭建 [rootlocalhost ~]#systemctl stop firewalld [rootlocalho…

CodeTop day3

class Solution {public int[] sortArray(int[] nums) {//这种方法超时【快速排序】for (int i0;i<nums.length-1;i){int minIndex i;//假设当前开始下标为最小元素下标for (int ji1;j<nums.length;j){//从i到nums。length-1区间里找到最小值下标if (nums[j]<nums[mi…