先来了解一下进程和线程
类比:
上述串行的代码示例就是一个程序,在使用python xx.py 运行时,内部就创建了一个进程(主进程),在进程中创建了一个线程(主线程),由线程逐行运行代码。
线程,是计算机中可以被cpu调度的最小单元,
进程,是计算机资源分配的最小单元(进程为线程提供资源)
一个进程中可以有多个线程,同一个进程中的线程可以共享此进程中的资源。
在Python中,多线程和多进程是用来实现并发执行的两种机制。这些系统调用(fork、spawn)是底层操作系统提供的方式,可以用来创建新的进程或者线程。
1、linux系统fork;
在Linux系统中,fork是一种创建进程的方式,它会复制当前进程的状态创建一个新的进程。新的进程称为子进程,而原始进程称为父进程。fork系统调用返回两次,一次在父进程中返回子进程的PID,一次在子进程中返回0。子进程会继承父进程的代码段、数据段、堆栈等信息。
2、window系统:spawn;
在Windows系统中,spawn是一种创建进程的方式,它通过调用CreateProcess函数来创建一个新的进程。与fork不同,spawn在Windows系统中不能直接复制当前进程的状态,而是需要指定要执行的程序路径。CreateProcess函数会创建一个新的进程,并且可以指定该进程的参数、环境变量等信息。
3、mac系统:fork和spawn (python3.8默认支持spawn)要是需要修改fork: multiprocessing.set_start_method(‘fork’)
在Mac系统中,fork和spawn两种方式都可以用来创建进程。fork与Linux中的fork类似,复制当前进程的状态创建新的进程。而spawn与Windows中的spawn类似,通过调用posix_spawn函数创建新的进程。
对于Python的多线程和多进程,可以使用threading和multiprocessing模块来实现。threading模块提供了线程相关的功能,可以创建和管理线程。multiprocessing模块则提供了多进程相关的功能,可以创建和管理进程。
因此,无论是在Linux、Windows还是Mac系统中,都可以使用fork或spawn来创建新的进程,并结合Python的多线程或多进程机制来实现并发执行的效果。具体选择哪种方式取决于所使用的操作系统和需求。
全局解释器锁(Global Interpreter Lock),是CPython解释器的功能,让一个进程中同一时刻只能有一个线程可以被CPU调用。
如果程序想利用计算机多核优势,让CPU同时处理一些任务,适合用多进程开发(即使资源开销大)。
常见的程序开发中,计算操作需要使用CPU多核优势,IO操作不需要利用多核优势,所以,就有一句话:
线程的常见方法
t.start(),当前线程准备就绪(等待CPU调度,具体时间是由CPU来决定)。
t.join(),等待当前线程的任务执行完毕后再向下继续执行。主线程等待子线程执行完毕在继续向下执行。
t.setDaemon(布尔值)守护线程(必须放在start之前)
线程名称的设置和获取 设置名字在start之前
import threading
def task(srd):
# 获取当前执行此代码的线程
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 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=v0200f3e0000bv52fpn5t6p007e34q1g"),
("罗斯mvp.mp4","https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f240000buuer5aa4tij4gv6ajgg")
]
for name,url in url_list:
t = DouYinThread(args=(name,url))
t.start()
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()
t = threading.Thread(target=_add, args=(loop,))
t1 = threading.Thread(target=_sub, args=(loop,))
t.start()
t1.start()
t.join()
t1.join()
print(number)
使用 with关键字加锁
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):
# t = threading.Thread(target=task)
# t.start()
def task1():
print("开始")
with lock_object:
global num
for i in range(1000000):
num += 1
print(num)
for i in range(2):
t = threading.Thread(target=task1)
t.start()
对于有些数据是安全的数据类型不需要加锁:
不安全的数据类型操
在程序中如果想要自己手动加锁,一般有两种:Lock和RLock。
多线程和进程的死锁是并发编程中常见的问题,它们指的是在多个线程或进程中相互等待对方释放资源而无法继续执行的情况。
import threading
# 创建资源对象
resourceA = threading.Lock()
resourceB = threading.Lock()
# 线程1获取资源A后,尝试获取资源B
def thread1():
resourceA.acquire()
resourceB.acquire()
# 执行操作...
resourceB.release()
resourceA.release()
# 线程2获取资源B后,尝试获取资源A
def thread2():
resourceB.acquire()
resourceA.acquire()
# 执行操作...
resourceA.release()
resourceB.release()
# 创建线程并启动
t1 = threading.Thread(target=thread1)
t2 = threading.Thread(target=thread2)
t1.start()
t2.start()
在上述代码中,线程1先获取了资源A,然后尝试获取资源B,而线程2先获取了资源B,然后尝试获取资源A。由于两个线程相互等待对方释放资源,导致了死锁的发生。
import multiprocessing
# 创建资源对象
resourceA = multiprocessing.Lock()
resourceB = multiprocessing.Lock()
# 进程1获取资源A后,尝试获取资源B
def process1():
resourceA.acquire()
resourceB.acquire()
# 执行操作...
resourceB.release()
resourceA.release()
# 进程2获取资源B后,尝试获取资源A
def process2():
resourceB.acquire()
resourceA.acquire()
# 执行操作...
resourceA.release()
resourceB.release()
# 创建进程并启动
p1 = multiprocessing.Process(target=process1)
p2 = multiprocessing.Process(target=process2)
p1.start()
p2.start()
在上述代码中,进程1先获取了资源A,然后尝试获取资源B,而进程2先获取了资源B,然后尝试获取资源A。同样地,由于两个进程相互等待对方释放资源,导致了死锁的发生。
线程不是开的越多越好,开的多了可能会导致系统的性能更低了,所以就引入线程池
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("start")
pool.shutdown(True) # 等待线程池中的任务执行完毕后,在继续执行(有点像线程的join方法)
print("end")
线程池任务完成可以干些别的事情(比如执行完成后,再让他做写其他事情,比如爬取数据后执行完后,再将其写入一个文件中)多任务
import time
from concurrent.futures import ThreadPoolExecutor
def task(video_url):
print("开始执行任务", video_url)
time.sleep(5)
def done(response):
print("任务执行后的返回值")
# 创建线程池,最多维护10个线程
pool = ThreadPoolExecutor(10)
url_list = ["www.xxxx-{}.com".format(i) for i in range(15)]
for url in url_list:
# 在线程池中提交一个任务,线程池中如果有空闲的线程,侧立马分配空前的线程去执行,执行完毕后再将线程交还给线程池,如果没有空闲的线程,则等待
futures = pool.submit(task,url)
futures.add_done_callback(done) # 时子主线程执行
print("start")
pool.shutdown(True) # 等待线程池中的任务执行完毕后,在继续执行(有点像线程的join方法)
print("end")
# 可以分工, 例如:task函数专门下载, Done函数 专门将下载的数据写入本地文件中
进程
线程
如有有些内容写的有问题,欢迎各位大神投稿!