-
线程相关概念
-
什么是线程
-
线程的创建开销小
-
进程与线程的区别
-
为和要用多线程
-
开启线程的两种方式
-
线程对象的方法
-
守护线程
-
互斥锁
-
线程相关概念
-
什么是线程?
-
线程:一个流水线的运行过程
进程内代码的运行过程
进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),
线程线程是一个执行单位,cpu执行的就是线程
多线程(即多个控制线程)的概念是,在一个进程中存在多个控制线程,多个控
制线程共享该进程的地址空间,相当于一个车间内有多条流水线,都共用一个车间的资源。
-
线程的创建开销小
-
创建进程的开销远大于线程
创建子进程要开一个内存空间,把父进程数据拷一份给子进程,该空间里至少要有一条流水线,这样开销很大
创建子线程资源不用申请,就用当前进程就可以,只是提交的时候有一段代码要并发运行,创建线程的开销要远远小于进程,几乎是这个请求发出的同时,这个线程立马就创建好了
-
进程之间是竞争关系,线程之间是协作关系?
-
进程与线程的区别
1.线程共享创建它的进程的地址空间;进程有自己的地址空间
2.线程可以直接访问其进程的数据段;进程有自己的父进程的数据段副本
3.线程可以直接与其进程中的其他线程通信;进程必须使用进程间通信来与同级进程通信
4.新线程很容易创建;新的进程需要父进程的复制。
5.线程可以对同一进程的线程进行相当大的控制;进程只能对子进程进行控制。
6.主线程的改变(取消,优先级的改变等)可能会影响进程中其他线程的行为;父进程的更改不会影响子进程
-
总结:
1、同一进程下的多个线程共享该进程的内存资源
2、开启子线程的开销要远远小于开启子进程
-
-
为何要用多线程
-
多线程指的是,在一个进程中开启多个线程,简单的讲:如果多个任务共用一块地址空间,那么必须在一个进程内开启多个线程。详细的讲分为4点:
-
多线程共享一个进程的地址空间
-
线程比进程更轻量级,线程比进程更容易创建可撤销,在许多操作系统中,创建一个线程比创建一个进程要快10-100倍,在有大量线程需要动态和快速修改时,这一特性很有用
-
若多个线程都是cpu密集型的,那么并不能获得性能上的增强,但是如果存在大量的计算和大量的I/O处理,拥有多个线程允许些活动彼此重叠运行,从而会加快程序执行的速度。
-
在多cpu系统中,为了最大限度的利用多核,可以开启多个线程,比开进程开销要小的多。(这一条并不适用于python)
-
-
开启线程的两种方式
-
threading模块介绍
开启线程的threading模块提供了一个比thread模块更高层的API来提供线程的并发性。这些线程并发运行并共享内存。
Thread类的使用 :目标函数可以实例化一个Thread对象,每个Thread对象代表着一个线程,可以通过start()方法,开始运行。
-
开启线程的方式一:每创建一个进程,默认有一个线程,就是主线程。进程要想执行,要先创建一个主线程,然后由这个进程内的线程去运行代码
# 1.证明开启子线程的开销小
from threading import Thread,current_thread
import os
def task():
# current_thread()会得到当前线程的线程对象.name能获取它的名字
print("%s is running" %current_thread().name)
if __name__ == '__main__': # Windows规定开启线程的代码必须放在它下面
t = Thread(target=task)
t.start() # 通知操作系统开启子线程
print('主线程',current_thread().name) #.name默认名,可以更改.name=名字
# 2.证明同一进程下的多个线程共享该进程的内存资源
from threading import Thread, current_thread
n = 100
def task():
global n # 声明n是全局变量
n = 0
if __name__ == '__main__':
t = Thread(target=task)
t.start() # 因为线程的运行太快了,有可能看到100
t.join() # 加join等线程运行完,打印永远看不到100的值
print("主线程",n)
- 开启线程的方式二:自己写一个子类去继承它
from threading import Thread
class Mythread(Thread): # 自定义的类必须继承Thread类
# 重写了init方法 父类就被覆盖掉
def __init__(self, name):
super().__init__() # 重用父类 因为父类还有很多有用功能,继承父类
self.name = name
def run(self) -> None: # 方法一定要写run
print("%s is running" % self.name)
if __name__ == '__main__':
# 实例化直接用自己自定义的类开子线程
t = Mythread("线程1") # args 为函数传参数 没有就不传
t.start() # 通知系统开启子线程
-
线程对象相关的方法
# Thread实例对象的方法 isAlive(): 返回线程是否活动的。 getName(): 返回线程名。 setName(): 设置线程名。 # threading模块提供的一些方法: threading.currentThread(): 返回当前的线程变量。 threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、 结束前,不包括启动前和终止后的线程。 threading.activeCount(): 返回正在运行的线程数量,len(threading.enumerate()) 有相同的结果。 group:线程组,目前还没有 实现 target: 要执行的方法,就是要执行的程序 是将被run()方法调用的可调用对象。默认为None,表示不调用任何东西 name:线程的名字,默认情况下以Thread-N的形式构造一个唯一的名字,N是一个小的十进制整数 args:给调用程序传入的值() kwargs:给程序传入的值,默认为{}
from threading import Thread,current_thread,active_count,enumerate
import os
import time
def task():
print("%s is running" %current_thread().name)
time.sleep(3)
if __name__ == '__main__':
t = Thread(target=task)
t.start()
# print(active_count()) # 查看活跃的线程数
print(enumerate()) # 显示列表里面放的是每个线程的对象,一个主线程的,一个线程1的
print("主") # 可以取出线程对象,去调对象下面的方法
# print(t.is_alive()) # 判断是否存活
# print('主线程',current_thread().name)
-
守护线程
守护进程是守护主进程的代码
守护线程是守护主线程的生命周期
无论是进程还是线程,都遵循:守护xxx会等待主xxx运行完毕后被销毁
需要强调的是:运行完毕并非终止运行
1.对主进程来说,运行完毕指的是主进程代码运行完毕
2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,
主线程才算运行完毕
详细解释:
1. 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直
等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束,
2. 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束
意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。
from threading import Thread,current_thread,active_count,enumerate
import os
import time
def task(n):
print("%s is running" %current_thread().name)
time.sleep(n)
print("%s is end" %current_thread().name)
if __name__ == '__main__':
t1 = Thread(target=task,args=(3,))
t2 = Thread(target=task,args=(5,))
t3 = Thread(target=task,args=(100,))
t3.daemon = True # 守护线程,守护主线程的生命周期
t1.start()
t2.start()
t3.start()
print("主") # 主线程5秒钟结束
"""
# 打印结果
Thread-1 is running
Thread-2 is running
Thread-3 is running
主
Thread-1 is end
Thread-2 is end
过程分析:
t1、t2、t3三个线程在背后都开始运行了,t1、t2都是正常的子线程,
t3则是守护线程,守护着主线程的生命周期 t3按正常来说是100秒结束,
但主线程在5秒后就结束了,一旦结束就直接带走t3了,主线程应该很快就
运行完了,但是要在原地等t1、t2运行完主进程才能结束,也就5秒钟,可
t3还没有运行完还是被一起带走了,所以无法看到Thread-3 is end
"""
-
互斥锁
在多任务操作系统中,同时运行的多个任务可能都需要使用同一种资源。这个过程有点类似于,公司部门里,我在使用着打印机打印东西的同时(还没有打印完),别人刚好也在此刻使用打印机打印东西,如果不做任何处理的话,打印出来的东西肯定是错乱的。
互斥锁是一种简单的加锁的方法来控制对共享资源的访问,对共享数据进行锁定,保证同一时刻只能有一个线程去操作。
【互斥锁的特点】:
-
原子性:把一个互斥量锁定为一个原子操作,这意味着操作系统保证了如果一个线程锁定了一个互斥量,没有其他线程在同一时间可以成功锁定这个互斥量;
-
唯一性:如果一个线程锁定了一个互斥量,在它解除锁定之前,没有其他线程可以锁定这个互斥量;
-
非繁忙等待:如果一个线程已经锁定了一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何cpu资源),直到第一个线程解除对这个互斥量的锁定为止,第二个线程则被唤醒并继续执行,同时锁定这个互斥量。
【互斥锁的操作流程如下】:
-
在访问共享资源后临界区域前,对互斥锁进行加锁;
-
在访问完成后释放互斥锁导上的锁。在访问完成后释放互斥锁导上的锁;
-
对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到锁被释放。对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到锁被释放。
-
from threading import Thread
import time
n = 100
def task():
global n
temp = n
time.sleep(0.1) # 设置延迟
n = temp - 1
if __name__ == "__main__":
n = 100
thread_l = []
for i in range(100):
t=Thread(target=task)
l.append(p)
t.start()
for obj in thread_l:
obj.join()
print("主",n,time.time()-start_time)
'''
代码运行完成结果大概率为99,小概率为98,运行时间则是略大于0.1秒。并发执行,速度快,数据不安全:
'''
需要注意:
join是等待所有,即整体串行,而锁只是锁住修改共享数据的部分,即部分串行,要想保证数据安全的根本原理在于让并发变成串行,join与互斥锁都可以实现,毫无疑问,互斥锁的部分串行效率要更高
from threading import Thread,Lock
import os
import time
n = 100
mutex = Lock()
def task():
global n
with mutex:
temp = n
time.sleep(0.1)
n = temp - 1
if __name__ == '__main__':
thread_l = []
start_time = time.time()
for i in range(100):
t = Thread(target=task)
thread_l.append(t)
t.start()
for obj in thread_l:
obj.join()
print("主",n,time.time()-start_time)
'''
代码运行结果则一定是0,但是这样也有一个缺点就是运行速度降低
了运行时间在10秒多一点,但是为了数据安全这些时间是必须的!
'''
提示:加上互斥锁,哪个线程抢到这个锁我们决定不了,哪个先抢到锁的那么就是哪个线程先执行,没有抢到的线程需要等待
加上互斥锁多任务瞬间变成单任务,性能会下降,也就是说同一时刻只能有一个线程去执行