并发编程(2)

2. 多线程开发

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 = 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)
  • 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
 t.start()
 
 print('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('日魔-{}'.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

    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)
    
    import threading
    
    lock_object = threading.RLock()
    
    loop = 10000000
    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()
    t2.start()
    
    t1.join()  # t1线程执行完毕,才继续往后走
    t2.join()  # t2线程执行完毕,才继续往后走
    
    print(number)
    
    

    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不支持。例如:

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

 ```python
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专门将下载的数据写入本地文件。
posted @ 2021-11-14 23:16  下个ID见  阅读(12)  评论(0)    收藏  举报