Python 并发编程

参考文献:https://gitee.com/wupeiqi/python_course

并发编程:提升代码执行的效率。原来需要 10 分钟执行,并发处理后可以加快到 1 分钟。

  • 初识进程和线程
  • 多线程开发
  • 线程安全
  • 线程锁

1.进程和线程:

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

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

import time
result = 0
for i in range(100000000):
result += i
print(result)
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=v0200f3e0000bv52fpn5t6p007e34q1g"),
("罗斯mvp.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f240000buuer5aa4tij4gv6ajqg")
] 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 GIL锁

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

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

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

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

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

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

  • 串行处理

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

    import time
    import multiprocessing def task(start, end, queue):
    result = 0
    for i in range(start, end):
    result += i
    queue.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) # 耗时: 2.6232550144195557

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

import multiprocessing
import threading def thread_task():
pass def 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.多线程开发

import threading

def task(arg):
print(arg) # 创建一个Thread对象(线程), 并封装线程被CPU 调度时应该执行的任务和相关参数。
t=threading.Thread(target=task,args=('xxx',))# 注意此处函数的后面不需要添加()
'''当元组只有一个元素时需要添加一个额外的逗号,表示是数据类型是元组''' # 线程准备就绪等待被CPU调度,代码继续向下执行
t.start()
print("继续执行。。。。")

线程的常见方法:

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

    import threading
    
    loop = 10000000
    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)
  • t.join(),等待当前线程的任务执行完毕后再向下继续执行。

    import threading
    
    number = 0
    
    def _add():
    global number
    for i in range(10000000):
    number += 1 t = threading.Thread(target=_add)
    t.start() t.join() # 主线程等待中... print(number)
    import threading
    
    number = 0
    
    def _add():
    global number
    for i in range(10000000):
    number += 1 def _sub():
    global number
    for 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)
    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()
    t2.start() t1.join() # t1线程执行完毕,才继续往后走
    t2.join() # t2线程执行完毕,才继续往后走 print(number)

2.1 守护线程

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

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

    import threading
    
    def task(arg):
    # 获取当前执行此代码的线程
    name = threading.current_thread().getName()
    print(name) # 创建10个线程。
    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):
    print('执行此线程', self._args) t = MyThread(args=(100,))
    t.start()
    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=v0200f3e0000bv52fpn5t6p007e34q1g"),
    ("罗斯mvp.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f240000buuer5aa4tij4gv6ajqg")
    ]
    for item in url_list:
    t = DouYinThread(args=(item[0], item[1]))
    t.start()

3.线程安全

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

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

  • 示例1: 某个数加减 10000000 次

    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()
    t2.start() t1.join() # t1线程执行完毕,才继续往后走
    t2.join() # t2线程执行完毕,才继续往后走 print(number) # 2755 出现线程安全问题,结果不唯一
    import threading
    
    lock_object = threading.RLock()# 设置锁对象。
    
    loop = 10000000
    number = 0 def _add(count):
    lock_object.acquire() # 加锁
    global number # 使用global 声名全局变量
    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()
    t2.start() t1.join() # t1线程执行完毕,才继续往后走
    t2.join() # t2线程执行完毕,才继续往后走 print(number)
    # 0
  • 示例2:

    import threading
    
    num = 0
    
    def task():
    global num
    for i in range(1000000):
    num += 1
    print(num) for i in range(2):
    t = threading.Thread(target=task)
    t.start()
    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()
    import threading
    
    num = 0
    lock_object = threading.RLock() def task():
    print("开始")
    with lock_object: # 基于上下文管理,内部自动执行 acquire 和 release
    global num
    for i in range(1000000):
    num += 1
    print(num) for i in range(2):
    t = threading.Thread(target=task)
    t.start()

注意:

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

import threading

data_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 threading
    
    num = 0
    lock_object = threading.Lock() 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,递归锁。

    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 # 程序员B开发了一个函数,可以直接调用这个函数。
def run():
print("其他功能")
func() # 调用程序员A写的func函数,内部用到了锁。
print("其他功能") # 程序员C开发了一个函数,自己需要加锁,同时也需要调用func函数。
def process():
with lock:
print("其他功能")
func() # ----------------> 此时就会出现多次锁的情况,只有RLock支持(Lock不支持)。
print("其他功能")

5.死锁

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

import threading

num = 0
lock_object = threading.Lock() def task():
print("开始")
lock_object.acquire() # 第1个抵达的线程进入并上锁,其他线程就需要再此等待。
lock_object.acquire() # 第1个抵达的线程进入并上锁,其他线程就需要再此等待。
global num
for i in range(1000000):
num += 1
lock_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 threading

def task(video_url):
pass url_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)
# 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) print("END")

示例2:等待线程池的任务执行完毕。

import time
from concurrent.futures import ThreadPoolExecutor def 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:任务执行完任务,再干点其他事。

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) # 是子主线程执行,传入参数是函数。 # 可以做分工,例如:task专门下载,done专门将下载的数据写入本地文件。

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

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) # 创建线程池,最多维护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())

案例:基于线程池下载豆瓣图片。

26044585,Hush,https://hbimg.huabanimg.com/51d46dc32abe7ac7f83b94c67bb88cacc46869954f478-aP4Q3V
19318369,柒十一,https://hbimg.huabanimg.com/703fdb063bdc37b11033ef794f9b3a7adfa01fd21a6d1-wTFbnO
15529690,Law344,https://hbimg.huabanimg.com/b438d8c61ed2abf50ca94e00f257ca7a223e3b364b471-xrzoQd
18311394,Jennah·,https://hbimg.huabanimg.com/4edba1ed6a71797f52355aa1de5af961b85bf824cb71-px1nZz
18009711,可洛爱画画,https://hbimg.huabanimg.com/03331ef39b5c7687f5cc47dbcbafd974403c962ae88ce-Co8AUI
30574436,花姑凉~,https://hbimg.huabanimg.com/2f5b657edb9497ff8c41132e18000edb082d158c2404-8rYHbw
17740339,小巫師,https://hbimg.huabanimg.com/dbc6fd49f1915545cc42c1a1492a418dbaebd2c21bb9-9aDqgl
18741964,桐末tonmo,https://hbimg.huabanimg.com/b60cee303f62aaa592292f45a1ed8d5be9873b2ed5c-gAJehO
30535005,TANGZHIQI,https://hbimg.huabanimg.com/bbd08ee168d54665bf9b07899a5c4a4d6bc1eb8af77a4-8Gz3K1
31078743,你的老杨,https://hbimg.huabanimg.com/c46fbc3c9a01db37b8e786cbd7174bbd475e4cda220f4-F1u7MX
25519376,尺尺寸,https://hbimg.huabanimg.com/ee29ee198efb98f970e3dc2b24c40d89bfb6f911126b6-KGvKes
21113978,C-CLong,https://hbimg.huabanimg.com/7fa6b2a0d570e67246b34840a87d57c16a875dba9100-SXsSeY
24674102,szaa,https://hbimg.huabanimg.com/0716687b0df93e8c3a8e0925b6d2e4135449cd27597c4-gWdv24
30508507,爱起床的小灰灰,https://hbimg.huabanimg.com/4eafdbfa21b2f300a7becd8863f948e5e92ef789b5a5-1ozTKq
12593664,yokozen,https://hbimg.huabanimg.com/cd07bbaf052b752ed5c287602404ea719d7dd8161321b-cJtHss
16899164,一阵疯,https://hbimg.huabanimg.com/0940b557b28892658c3bcaf52f5ba8dc8402100e130b2-G966Uz
847937,卩丬My㊊伴er彎,https://hbimg.huabanimg.com/e2d6bb5bc8498c6f607492a8f96164aa2366b104e7a-kWaH68
31010628,慢慢即漫漫,https://hbimg.huabanimg.com/c4fb6718907a22f202e8dd14d52f0c369685e59cfea7-82FdsK
13438168,海贼玩跑跑,https://hbimg.huabanimg.com/1edae3ce6fe0f6e95b67b4f8b57c4cebf19c501b397e-BXwiW6
28593155,源稚生,https://hbimg.huabanimg.com/626cfd89ca4c10e6f875f3dfe1005331e4c0fd7fd429-9SeJeQ
28201821,合伙哼哼,https://hbimg.huabanimg.com/f59d4780531aa1892b80e0ec94d4ec78dcba08ff18c416-769X6a
28255146,漫步AAA,https://hbimg.huabanimg.com/3c034c520594e38353a039d7e7a5fd5e74fb53eb1086-KnpLaL
30537613,配䦹,https://hbimg.huabanimg.com/efd81d22c1b1a2de77a0e0d8e853282b83b6bbc590fd-y3d4GJ
22665880,日后必火,https://hbimg.huabanimg.com/69f0f959979a4fada9e9e55f565989544be88164d2b-INWbaF
16748980,keer521521,https://hbimg.huabanimg.com/654953460733026a7ef6e101404055627ad51784a95c-B6OFs4
30536510,“西辞”,https://hbimg.huabanimg.com/61cfffca6b2507bf51a507e8319d68a8b8c3a96968f-6IvMSk
30986577,艺成背锅王,https://hbimg.huabanimg.com/c381ecc43d6c69758a86a30ebf72976906ae6c53291f9-9zroHF
26409800,CsysADk7,https://hbimg.huabanimg.com/bf1d22092c2070d68ade012c588f2e410caaab1f58051-ahlgLm
30469116,18啊全阿,https://hbimg.huabanimg.com/654953460733026a7ef6e101404055627ad51784a95c-B6OFs4
17473505,椿の花,https://hbimg.huabanimg.com/0e38d810e5a24f91ebb251fd3aaaed8bb37655b14844c-pgNJBP
19165177,っ思忆゜♪,https://hbimg.huabanimg.com/4815ea0e4905d0f3bb82a654b481811dadbfe5ce2673-vMVr0B
16059616,格林熊丶,https://hbimg.huabanimg.com/8760a2b08d87e6ed4b7a9715b1a668176dbf84fec5b-jx14tZ
30734152,sCWVkJDG,https://hbimg.huabanimg.com/f31a5305d1b8717bbfb897723f267d316e58e7b7dc40-GD3e22
24019677,虚无本心,https://hbimg.huabanimg.com/6fdfa9834abe362e978b517275b06e7f0d5926aa650-N1xCXE
16670283,Y-雨后天空,https://hbimg.huabanimg.com/a3bbb0045b536fc27a6d2effa64a0d43f9f5193c177f-I2vHaI
21512483,汤姆2,https://hbimg.huabanimg.com/98cc50a61a7cc9b49a8af754ffb26bd15764a82f1133-AkiU7D
16441049,笑潇啸逍小鱼,https://hbimg.huabanimg.com/ae8a70cd85aff3a8587ff6578d5cf7620f3691df13e46-lmrIi9
24795603,⁢⁢⁢⁢⁢v,https://hbimg.huabanimg.com/a7183cc3a933aa129d7b3230bf1378fd8f5857846cc5-3tDtx3
29819152,妮玛士珍多,https://hbimg.huabanimg.com/ca4ecb573bf1ff0415c7a873d64470dedc465ea1213c6-RAkArS
19101282,陈勇敢,https://hbimg.huabanimg.com/ab6d04ebaff3176e3570139a65155856871241b58bc6-Qklj2E
28337572,爱意随风散,https://hbimg.huabanimg.com/117ad8b6eeda57a562ac6ab2861111a793ca3d1d5543-SjWlk2
17342758,幸运instant,https://hbimg.huabanimg.com/72b5f9042ec297ae57b83431123bc1c066cca90fa23-3MoJNj
18483372,Beau染,https://hbimg.huabanimg.com/077115cb622b1ff3907ec6932e1b575393d5aae720487-d1cdT9
22127102,栽花的小蜻蜓,https://hbimg.huabanimg.com/6c3cbf9f27e17898083186fc51985e43269018cc1e1df-QfOIBG
13802024,LoveHsu,https://hbimg.huabanimg.com/f720a15f8b49b86a7c1ee4951263a8dbecfe3e43d2d-GPEauV
22558931,白驹过隙丶梨花泪う,https://hbimg.huabanimg.com/e49e1341dfe5144da5c71bd15f1052ef07ba7a0e1296b-jfyfDJ
11762339,cojoy,https://hbimg.huabanimg.com/5b27f876d5d391e7c4889bc5e8ba214419eb72b56822-83gYmB
30711623,雪碧学长呀,https://hbimg.huabanimg.com/2c288a1535048b05537ba523b3fc9eacc1e81273212d1-nr8M4t
18906718,西霸王,https://hbimg.huabanimg.com/7b02ad5e01bd8c0a29817e362814666a7800831c154a6-AvBDaG
31037856,邵阳的小哥哥,https://hbimg.huabanimg.com/654953460733026a7ef6e101404055627ad51784a95c-B6OFs4
26830711,稳健谭,https://hbimg.huabanimg.com/51547ade3f0aef134e8d268cfd4ad61110925aefec8a-NKPEYX
import os
import requests
from concurrent.futures import ThreadPoolExecutor def download(file_name, image_url):
res = requests.get(
url=image_url,
headers={
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
}
)
# 检查images目录是否存在?不存在,则创建images目录
if not os.path.exists("images"):
# 创建images目录
os.makedirs("images")
file_path = os.path.join("images", file_name)
# 2.将图片的内容写入到文件
with open(file_path, mode='wb') as img_object:
img_object.write(res.content) # 创建线程池,最多维护10个线程。
pool = ThreadPoolExecutor(10) with open("mv.csv", mode='r', encoding='utf-8') as file_object:
for line in file_object:
nid, name, url = line.split(",")
file_name = "{}.png".format(name)
pool.submit(download, file_name, url)
import os
import requests
from concurrent.futures import ThreadPoolExecutor def download(image_url):
res = requests.get(
url=image_url,
headers={
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
}
)
return res def outer(file_name):
def save(response):
res = response.result()
# 写入本地
# # 检查images目录是否存在?不存在,则创建images目录
if not os.path.exists("images"):
# 创建images目录
os.makedirs("images") file_path = os.path.join("images", file_name) # # 2.将图片的内容写入到文件
with open(file_path, mode='wb') as img_object:
img_object.write(res.content) return save # 创建线程池,最多维护10个线程。
pool = ThreadPoolExecutor(10) with open("mv.csv", mode='r', encoding='utf-8') as file_object:
for line in file_object:
nid, name, url = line.split(",")
file_name = "{}.png".format(name)
fur = pool.submit(download, url)
fur.add_done_callback(outer(file_name))# 传入参数,可以使用闭包的形式。

7.单例模式(扩展)

面向对象 + 多线程相关的一个面试题(以后项目和源码中会用到)。

之前写一个类,每次执行 类() 都会实例化一个类的对象。

class Foo:
pass obj1 = Foo() obj2 = Foo()
print(obj1,obj2)

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

  • 简单的实现单例模式

    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 def __init__(self, name):
    self.name = name def __new__(cls, *args, **kwargs):
    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()
  • 加锁解决BUG

    import threading
    import time
    class Singleton:
    instance = None
    lock = threading.RLock() def __init__(self, name):
    self.name = name def __new__(cls, *args, **kwargs):
    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()
  • 加判断,提升性能

    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() # 执行1000行代码 data = Singleton('asdfasdf')
    print(data)

本篇总结

  1. 进程和线程的区别
  2. 什么是GIL锁
  3. 多线程和线程池的使用。
  4. 线程安全 & 线程锁 & 死锁
  5. 单例模式

Python 并发编程(上)的更多相关文章

  1. 快速了解Python并发编程的工程实现(上)

    关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...

  2. Python并发编程__多进程

    Python并发编程_多进程 multiprocessing模块介绍 python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大 ...

  3. Python并发编程之深入理解yield from语法(八)

    大家好,并发编程 进入第八篇. 直到上一篇,我们终于迎来了Python并发编程中,最高级.最重要.当然也是最难的知识点--协程. 当你看到这一篇的时候,请确保你对生成器的知识,有一定的了解.当然不了解 ...

  4. Python并发编程二(多线程、协程、IO模型)

    1.python并发编程之多线程(理论) 1.1线程概念 在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程 线程顾名思义,就是一条流水线工作的过程(流水线的工作需要电源,电源就相当于 ...

  5. Python并发编程一(多进程)

    1.背景知识(进程.多道技术) 顾名思义,进程即正在执行的一个过程.进程是对正在运行程序的一个抽象. 进程的概念起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重要的抽象概念之一 ...

  6. 《转载》Python并发编程之线程池/进程池--concurrent.futures模块

    本文转载自Python并发编程之线程池/进程池--concurrent.futures模块 一.关于concurrent.futures模块 Python标准库为我们提供了threading和mult ...

  7. Python并发编程系列之多线程

    1 引言 上一篇博文详细总结了Python进程的用法,这一篇博文来所以说Python中线程的用法.实际上,程序的运行都是以线程为基本单位的,每一个进程中都至少有一个线程(主线程),线程又可以创建子线程 ...

  8. Python并发编程系列之多进程(multiprocessing)

    1 引言 本篇博文主要对Python中并发编程中的多进程相关内容展开详细介绍,Python进程主要在multiprocessing模块中,本博文以multiprocessing种Process类为中心 ...

  9. python并发编程&多线程(二)

    前导理论知识见:python并发编程&多线程(一) 一 threading模块介绍 multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性 官网链 ...

随机推荐

  1. 解决matplotlib中文不显示问题

    在导入库时添加如下几行代码 from pylab import mpl mpl.rcParams['font.sans-serif'] = ['FangSong'] # 指定默认字体 mpl.rcPa ...

  2. 在Linux虚拟机上挂载文件卷

    一 通过跳板机 将卷挂载在ec2 实例上的方法. 1 查询 机器上挂载了那些卷? // lsblk 是否已经是挂载卷 查看后面的目录 如果没有就是未挂载. 2 操作未挂载卷? /* sudo file ...

  3. 在 Dapr 中使用 Cron 绑定的计划任务

    我昨天写了一篇关于在微服务应用程序中采用Dapr的好处的文章<从服务之间的调用来看 我们为什么需要Dapr>[1], 在那篇文章中,我们专注于"服务调用"构建块 [2] ...

  4. Java8特性大全(最新版)

    一.序言 Java8 是一个里程碑式的版本,凭借如下新特性,让人对其赞不绝口. Lambda 表达式给代码构建带来了全新的风格和能力: Steam API 丰富了集合操作,拓展了集合的能力: 新日期时 ...

  5. 洛谷 P4708 画画(无标号欧拉子图计数)

    首先还是类似于无标号无向图计数那样,考虑点的置换带动边的置换,一定构成单射,根据 Burnside 引理: \[|X / G| = \frac{1}{|G|}\sum\limits_{g \in G} ...

  6. Java.lang.Math类详解

    Math类位于Java.lang包中,包含用于执行基本数学运算的方法!Math类的所有执行方法都是静态方法,可以直接使用类名.方法名调用,如:Math.round() 常用的方法:Math.round ...

  7. select 级联选择

    转载请注明来源:https://www.cnblogs.com/hookjc/ <script   language="javascript">   <!--   ...

  8. Web容器中DefaultServlet详解

    万分感谢原文作者:_licho 原文链接:https://blog.csdn.net/qq_30920821/article/details/78328608 Web容器中DefaultServlet ...

  9. java基础之设计模式之单例模式

    关于单例模式: 单例,即单一实例.因为在一些情况下,某些类的对象,我们只需要一个就可以了,所以我们要用到单例模式. 单例模式的目的是使得一个类中的一个静态对象成为系统中的唯一实例,提供一个访问该实例的 ...

  10. 通过bindservice方式调用服务方法里面的过程

    为什么要引入bindService:目的为了调用服务里面的方法 (1)定义一个服务 服务里面有一个方法需要Activity调用 (2)定义一个中间人对象(IBinder) 继承Binder (3)在o ...