https://blog.csdn.net/somezz/article/details/80963760
线程(Thread)也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
---------------------
每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。线程有 就绪、阻塞、运行 三种基本状态。
---------------------
说到 Python 中的多线程,一个绕不过去的话题就是全局锁 GIL(Global interpreter lock)。GIL 本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。
首先明确我们线程执行的任务是什么,是做计算(计算密集型)还是做输入输出(I/O 密集型),不同地场景使用不同的方法。Python 多线程适合用在 I/O 密集型任务中。I/O 密集型任务较少时间用在 CPU 计算上,较多时间用在 I/O 上,如文件读写,web 请求,数据库请求 等;而对于计算密集型任务,应该使用多进程。
Python标准库自带了两个多线程模块,分别是threading和thread,其中,thread是低级模块,threading是对thread的分装,一般,我们直接使用threading即可。
threading 模块提供的其他方法:
除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:
下面的代码设置了5个线程调用say_hello函数
import threading
import time
def say_hello():
time.sleep(1)
print("Hello world!")
def main():
threads = []
for i in range(5):
thread = threading.Thread(target=say_hello)
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
print('hello')
main()
输出
Hello world!
Hello world!
Hello world!
Hello world!
Hello world!
hello
jion()函数阻塞主线程的运行。没有使用join()函数,线程执行顺序不定,主线程可能在所有子线程执行完之前就执行了。
import threading
import time
def say_hello():
time.sleep(1)
print("Hello world!")
def main():
for i in range(5):
thread = threading.Thread(target=say_hello)
thread.start()
print('hello')
main()
输出
hello
Hello world!
Hello world!
Hello world!
Hello world!
Hello world!
Python 多线程适合用在 I/O 密集型任务中。I/O 密集型任务较少时间用在 CPU 计算上,较多时间用在 I/O 上,如文件读写,web 请求,数据库请求 等;而对于计算密集型任务,应该使用多进程。
https://blog.csdn.net/somezz/article/details/80963760
如果多个线程共同对某个数据修改,则可能出现不可预料的结果,这个时候就需需要使用互斥锁来进步同步。如下所示的代码,在三个线程对共同变量 num 进行 100 万次加减操作之后,其 num 的结果不为 0,
import time, threading
num = 0
lock = threading.Lock()
def task_thread(n):
global num
for i in range(1000000):
num = num + n
num = num - n
t1 = threading.Thread(target=task_thread, args=(6,))
t2 = threading.Thread(target=task_thread, args=(17,))
t3 = threading.Thread(target=task_thread, args=(11,))
t1.start(); t2.start(); t3.start()
t1.join(); t2.join(); t3.join()
print("except value is 0, real value is {}".format(num))
运行结果
except value is 0, real value is 23
之所以会出现不为 0 的情况,因为修改 num 需要多条语句,当一个线程正在执行 num+n 时,另一个线程正在执行 num-m ,从而导致之前的线程执行 num-n 时 num 的值已不是之前的值,从而导致最终的结果不为 0 。
为了保证数据的正确性,需要使用互斥锁对多个线程进行同步,限制当一个线程正在访问数据时,其他只能等待,直到前一线程释放锁。使用 threading.Thread 对象的 Lock 和 Rlock 可以实现简单的线程同步,这两个对象都有 acquire 方法和 release 方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire 和 release 方法之间。如下:
import time, threading
num = 0
lock = threading.Lock()
def task_thread(n):
global num
# 获取锁,用于线程同步
lock.acquire()
for i in range(1000000):
num = num + n
num = num - n
#释放锁,开启下一个线程
lock.release()
t1 = threading.Thread(target=task_thread, args=(6,))
t2 = threading.Thread(target=task_thread, args=(17,))
t3 = threading.Thread(target=task_thread, args=(11,))
t1.start(); t2.start(); t3.start()
t1.join(); t2.join(); t3.join()
print("except value is 0, real value is {}".format(num))
except value is 0, real value is 0
也可以使用with lock:
import time, threading
num = 0
lock = threading.Lock()
def task_thread(n):
global num
with lock:
for i in range(1000000):
num = num + n
num = num - n
t1 = threading.Thread(target=task_thread, args=(6,))
t2 = threading.Thread(target=task_thread, args=(17,))
t3 = threading.Thread(target=task_thread, args=(11,))
t1.start(); t2.start(); t3.start()
t1.join(); t2.join(); t3.join()
print("except value is 0, real value is {}".format(num))
https://www.jianshu.com/p/afd9b3deb027
传统多线程方案会使用“即时创建, 即时销毁”的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器将处于不停的创建线程,销毁线程的状态。
一个线程的运行时间可以分为3部分:线程的启动时间、线程体的运行时间和线程的销毁时间。在多线程处理的情景中,如果线程不能被重用,就意味着每次创建都需要经过启动、销毁和运行3个过程。这必然会增加系统相应的时间,降低了效率。
使用线程池:
由于线程预先被创建并放入线程池中,同时处理完当前任务之后并不销毁而是被安排处理下一个任务,因此能够避免多次创建线程,从而节省线程创建和销毁的开销,能带来更好的性能和系统稳定性。
http://c.biancheng.net/view/2627.html
线程池的基类是 concurrent.futures 模块中的 Executor,Executor 提供了两个子类,即 ThreadPoolExecutor 和 ProcessPoolExecutor,其中 ThreadPoolExecutor 用于创建线程池,而 ProcessPoolExecutor 用于创建进程池。
如果使用线程池/进程池来管理并发编程,那么只要将相应的 task 函数提交给线程池/进程池,剩下的事情就由线程池/进程池来搞定。
Exectuor 提供了如下常用方法:
程序将 task 函数提交(submit)给线程池后,submit 方法会返回一个 Future 对象,Future 类主要用于获取线程任务函数的返回值。由于线程任务会在新线程中以异步方式执行,因此,线程执行的函数相当于一个“将来完成”的任务,所以 Python 使用 Future 来代表。
Future 提供了如下方法:
在用完一个线程池后,应该调用该线程池的 shutdown() 方法,该方法将启动线程池的关闭序列。调用 shutdown() 方法后的线程池不再接收新任务,但会将以前所有的已提交任务执行完成。当线程池中的所有任务都执行完成后,该线程池中的所有线程都会死亡。
使用线程池来执行线程任务的步骤如下:
from concurrent.futures import ThreadPoolExecutor
import time
# 定义一个准备作为线程任务的函数
def action(max):
time.sleep(2)
return max
# 创建一个包含2条线程的线程池
pool = ThreadPoolExecutor(max_workers=2)
# 向线程池提交一个task, 50会作为action()函数的参数
future1 = pool.submit(action, 50)
# 向线程池再提交一个task, 100会作为action()函数的参数
future2 = pool.submit(action, 100)
# 关闭线程池
pool.shutdown()
另外,由于线程池实现了上下文管理协议(Context Manage Protocol),因此,程序可以使用 with 语句来管理线程池,这样即可避免手动关闭线程池,如上面的程序所示。
from concurrent.futures import ThreadPoolExecutor
import time
# 定义一个准备作为线程任务的函数
def action(max):
time.sleep(2)
return max
# 创建一个包含2条线程的线程池
with ThreadPoolExecutor(max_workers=2) as pool:
# 向线程池提交一个task, 50会作为action()函数的参数
future1 = pool.submit(action, 50)
# 向线程池再提交一个task, 100会作为action()函数的参数
future2 = pool.submit(action, 100)
# 查看future1代表的任务返回的结果
print(future1.result())
# 查看future2代表的任务返回的结果
print(future2.result())
此外,Exectuor 还提供了一个 map(func, *iterables, timeout=None, chunksize=1)
方法,该方法的功能类似于全局函数 map(),区别在于线程池的 map() 方法会为 iterables 的每个元素启动一个线程,以并发方式来执行 func 函数。这种方式相当于启动 len(iterables) 个线程,井收集每个线程的执行结果。
例如,如下程序使用 Executor 的 map() 方法来启动线程,并收集线程任务的返回值:
from concurrent.futures import ThreadPoolExecutor
import time
# 定义一个准备作为线程任务的函数
def action(max):
time.sleep(2)
return max
# 创建一个包含2条线程的线程池
with ThreadPoolExecutor(max_workers=2) as pool:
# 向线程池提交4个task, (50,100,150,200)会作为action()函数的参数
results = pool.map(action, (50,100,150,200))
for result in results:
# 查看future代表的任务返回的结果
print(result)
https://www.jianshu.com/p/53c2e732d974
concurrent.futures
库提供了一个 ProcessPoolExecutor
类, 可被用来在一个单独的Python解释器中执行计算密集型函数。
from concurrent.futures import ProcessPoolExecutor
import time
def work(x):
time.sleep(2)
return result
# Parallel implementation
with ProcessPoolExecutor() as pool:
results = pool.map(action, (50,100,150,200))
for result in results:
# 查看future代表的任务返回的结果
print(result)
像多线程那样可以使用pool.submit()手动提交单个任务。
python 多线程下载图片
https://www.52pojie.cn/thread-912305-1-1.html?tdsourcetag=s_pctim_aiomsg
Python多进程处理图片
https://blog.csdn.net/Alvin_FZW/article/details/82886004