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. python 小兵(9)生成器

    生成器 首先我们来看看什么是个生成器,生成器本质就是迭代器 在python中有三种方式来获取生成器 1.通过生成器函数 2.通过各种推到式来实现生成器 3.通过数据的转换也可以获取生成器 首先,我们先 ...

  2. nextcloud个人云搭建

    nextcloud个人云搭建 目录 nextcloud个人云搭建 树莓派安装系统 安装OMV5 安装dockcer 挂载硬盘进行映射(使用u盘测试的) 不足 配置数据库 使用docker拉取postg ...

  3. resp.getWriter().print(categoryList)、resp.getWriter().write(String)与new ObjectMapper().writeValue(resp.getOutputStream(),categoryList)的区别

    前言:最近在复习原生的servlet的时候,对其输出流不理解,故总结一下: resp.getWriter().print(categoryList) 可以输出字符串,也可以输出对象,可能还有其他类型, ...

  4. root登陆530 Permission denied、530 Login incorrect解决

    感谢大佬:https://blog.51cto.com/3241766/2316986?source=dra 背景:由于云平台上22端口不对外放开,sftp使用不了,故选择ftp服务 操作系统版本: ...

  5. 【转】python导出依赖库

    原文链接:https://www.cnblogs.com/ceshixuexi/p/8283372.html 在Python开发的过程中,经常会遇到各种各样的小问题,比如在一台计算机上调试好的程序,迁 ...

  6. iOS 学习资料Blog 技术论坛等,不断添加中。。。。

    iOS 学习资料整理 http://www.jianshu.com/p/dc81698a873c    中文 iOS/Mac 开发博客列表  https://github.com/tangqiaobo ...

  7. NoSQL 之 Redis配置与优化

    NoSQL 之 Redis配置与优化 1.关系数据库与非关系型数据库概述 2.关系数据库与非关系型数据库区别 3.非关系型数据库产生背景 4.Redis简介 5.Redis安装部署 6.Redis 命 ...

  8. (Elementui) el-tree 中英文过滤以及搜索到父子显示子节点,搜索到子节点显示父节点(filter-node-method)

    案例下载:https://gitee.com/tudoumlp/just1.git   (vue-ele-demo) 在项目中,会遇到树节点的搜索,中文和英文搜索,以及搜索到父节点匹配的时候同步显示该 ...

  9. Git忽略文件.gitignore的使用

    本博客旨在自我学习使用,如有任何疑问请及时联系博主 1.WHY? 当你使用git add .的时候有没有遇到把你不想提交的文件也添加到了缓存中去?比如项目的本地配置信息,如果你上传到Git中去其他人p ...

  10. CSS3带你实现3D转换效果

    前言 在css3中允许使用3D转换来对元素进行格式化,在原本只是2D转化的平面中引入了Z轴.在这之前我们讲解了css3中的2D转换,也就是二维空间变换,本篇的3D转换就是基于原来的2D转换而来,与2D ...