并发编程线程
并发编程 - 线程
1、什么是线程
进程:资源单位
线程:执行单位
线程与进程都是虚拟的概念,只是为了更好地表达某种事物
注意:开启一个进程,一定会自带一个线程,线程才是真正的执行者
2、为什么要使用线程
> 为了节省资源的占用
> 多线程执行速度非常快
开启进程会发生什么:
> 启动进程会产生一个内存空间,申请一块资源
> 会自带一个主线程
> 开启子进程的速度要比开启子线程的速度慢
开启线程会发生什么:
> 一个进程内可以开启多个线程,从进程的内存空间中申请资源
> 节省资源
# 比如开启三个进程:
# 占用三分内存资源
# 比如开启三个线程:
# 从一个内存资源中,申请三个小的执行单位
问题1:在单核情况下,开启多进程有没有提高执行效率?
没有,在单核情况下,尽量开启多线程。
问题:IO密集型、计算密集型分别使用什么?
IO密集型:多线程
IO(时间由用户决定):
阻塞:切换+保存状态
计算密集型:多进程
计算(时间由操作系统定):
计算时间很长:切换+保存状态
注意:进程与进程之间数据是隔离的,线程与线程之间的数据是共享的
3、线程的两种创建方式、守护线程
守护线程的使用方式和守护进程的使用方式一样,在下面创建线程中体现:
第一种方式:直接调用Thread类
from threading import Thread # Thread用来创建线程from threading import current_thread # current_thread中含有线程的属性
import time
def task():
print(f"start...{current_thread().name}") # current_thread().name 线程的名字
time.sleep(1)
print(f"end...{current_thread().name}") # current_thread().name 线程的名字
if__name__ == "__main__":
# 模拟开启10个子线程
for i in range(10):
# 开启子线程
t = Thread(target=task)
# 加上守护线程:主进程结束,代表主线程也结束,子线程有可能会被回收,将其他线程回收资源
t.daemon = True
t.start()
# 告诉主线程等到子线程执行完成再执行主线程
t.join()
print(f"主进程(主线程)结束...{current_thread().name}") # current_thread().name 线程的名字
执行结果:
start...Thread-1end...Thread
-1start...Thread
-2end...Thread
-2start...Thread
-3end...Thread
-3start...Thread
-4end...Thread
-4start...Thread
-5end...Thread
-5start...Thread
-6end...Thread
-6start...Thread
-7end...Thread
-7start...Thread
-8end...Thread
-8start...Thread
-9end...Thread
-9start...Thread
-10end...Thread
-10主进程(主线程)结束...MainThread
第二种方式:继承Thread类
from threading import Thread # Thread用来创建线程from threading import current_thread # current_thread中含有线程的属性
import time
class MyThread(Thread):
def run(self):
print(f"start...{current_thread().name}") # current_thread().name 线程的名字
time.sleep(1)
print(f"end...{current_thread().name}") # current_thread().name 线程的名字
if__name__ == "__main__":
# 开启子线程
t = MyThread()
# 加上守护线程:主进程结束,代表主线程也结束,子线程有可能会被回收,将其他线程回收资源
t.daemon = True
t.start()
# 告诉主线程等到子线程执行完成再执行主线程
t.join()
print(f"主进程(主线程)结束...{current_thread().name}") # current_thread().name 线程的名字
执行结果:
start...Thread-1end...Thread
-1主进程(主线程)结束...MainThread
4、线程间数据是互通的
from threading import Threadimport timenumber
= 100def task():
global number
number += 100
print("start...")
time.sleep(1)
print("end...")
if__name__ == "__main__":
# 开启子线程
t = Thread(target=task)
t.start()
# 告诉主线程等到子线程执行完成再执行主线程
t.join()
print("主进程(主线程)开始...")
print(number)print("主进程(主线程)结束...")
执行结果:
start...end...
主进程(主线程)开始...
200主进程(主线程)结束...
5、线程互斥锁
互斥锁是一把锁,将并发编程串行,牺牲了效率,保证了数据读写安全
多线程实现并发会造成数据不安全,可以使用互斥锁来避免这种问题
例:开启十个线程,对一个数据进行修改
from threading import Threadfrom threading import Lockimport timenumber
= 100def task(lock):
global number
# 加锁
lock.acquire()
number2 = number
time.sleep(0.5)
number = number2 - 1
# 释放锁
lock.release()
if__name__ == "__main__":
lock = Lock()
list1 = []
for i in range(10):
t = Thread(target=task, args=(lock, ))
t.daemon = True
t.start()
list1.append(t)
for t in list1:
t.join()
print(number)
执行结果:
90
6、线程池
作用:用来限制线程的数量,保证了硬件跟得上软件的发展
from concurrent.futures import ThreadPoolExecutor# pool限制一次只能创建10个线程,如果要创建50个线程,就要分成5次创建pool = ThreadPoolExecutor(10)
def task(i):print(i)
if__name__ == "__main__":
# 开启50个线程
for i in range(50):
# submit(函数名, 参数)方法实现并发pool.submit(task, 123)
执行结果:
123123
123
123
123
123
123
123
123
123
123123
123
123
123
123
123
123
123123
123
123
123123
123
123
123
123
123
123
123
123
123
123
123123123
123
123
123
123
123
123
123
123
123
123
123
123
123
7、线程池中的回调函数callback
from concurrent.futures import ThreadPoolExecutor# 并发执行的任务def task1():
return"壹贰弎肆伍陆柒捌玖拾"
# 自定义回调函数
def task2(obj): # obj对象中的result方法会返回 "壹贰弎肆伍陆柒捌玖拾"
result = obj.result() # "壹贰弎肆伍陆柒捌玖拾"
print(result)
if__name__ == "__main__":
pool = ThreadPoolExecutor(5)
for i in range(5):
# pool.submit(函数名).add_done_callback(回调函数的名字)
# submit(函数名, 函数接收的参数1, 参数2...)
pool.submit(task1).add_done_callback(task2)
执行结果:
壹贰弎肆伍陆柒捌玖拾壹贰弎肆伍陆柒捌玖拾
壹贰弎肆伍陆柒捌玖拾
壹贰弎肆伍陆柒捌玖拾
壹贰弎肆伍陆柒捌玖拾
PS:进程池中的回调函数和线程池中的回调函数使用方式一样
以上是 并发编程线程 的全部内容, 来源链接: utcz.com/z/537886.html