Python学习day38-并发编程(线程)
线程的概念
前面我们已经了解了进程的概念,我们知道,进程有很多的优点,他提供了多道编程以及并发的方式,可以让每个进程都有自己的CPU和各种资源,极大的提高了计算机的效率,那么为什么还要引入线程的概念呢,我们就用多进程来编程不好么?
答案就是,不好,因为进程也是有缺陷的,主要体现在两个方面:一个就是一个进程在同一时间只能干一件事情,同时开过多的子进程的话会过量的占用系统资源,另外一点就是,进程在执行过程中如果被阻塞的话,整个进程就会挂起,不会继续往下运行,也不会自己结束,一直占据着系统的一部分资源,造成浪费.
所以线程到底是什么呢?答案就出来了:
- 进程是资源分配最小的单位,而线程则是CPU调度的最小单位,每一个进程中至少有一个线程,注意,至少一个,多者不限.
开启线程的两种方式
线程主要用的模块是threading,导入的是Thread类,以此来实例化开启线程,实际开启线程的方式与开启进程十分相似,见下例:
xxxxxxxxxx
18
1
# 方式一
2
# 通过定义函数的方式,在main函数里面直接调用Thread来开启线程
3
from threading import Thread
4
import time
5
6
7
def task():
8
print('线程start')
9
time.sleep(2)
10
print('线程end')
11
12
13
if __name__ == '__main__':
14
t = Thread(target=task)
15
t.start() # 告诉操作系统开一个线程
16
# t.join()
17
print('主线程')
18
xxxxxxxxxx
21
1
# 方式二
2
# 通过定义类继承Thread的方式,直接实例化类就可以调用从而开启线程
3
from threading import Thread
4
import time
5
6
7
class Myt(Thread):
8
def __init__(self):
9
super().__init__()
10
11
def run(self):
12
print('线程start')
13
time.sleep(2)
14
print('线程end')
15
16
17
if __name__ == '__main__':
18
t = Myt()
19
t.start()
20
print('主线程')
21
线程和进程
线程和进程的区别大致可以总结为以下四种:
-
地址空间以及其他系统资源:
- 进程间是相互独立的
- 同一个进程的所有线程之间是共享该进程的所有资源的.
-
通信:
- 进程间通信是通过IPC,这点我们之前了解过
- 同一个进程的所有县城之间都是可以直接读写数据的,但是我们需要用进程同步以及互斥的手段来保证数据的一致性.
-
调度和切换:
- 同一个进程的线程上下文之间的切换比进程上下文之间的切换要快很多,这点我们将在下面用实例来证明
-
多线程操作系统里面,进程不是一个可执行的实体,但是线程是的.
下面我们用几个实例来证明这几个区别:
xxxxxxxxxx
24
1
# 线程共享资源
2
from multiprocessing import Process
3
from threading import Thread
4
import time
5
6
7
8
def task():
9
global x
10
x = 0
11
12
13
if __name__ == '__main__':
14
x = 100
15
# t = Thread(target=task)
16
# t.start()
17
# t.join()
18
# print(x)
19
# 0 这里可以看出同一个进程下的线程对于全局变量修改的话,是共享该进程的所有资源的
20
p = Process(target=task)
21
p.start()
22
p.join()
23
print(x)
24
# 100 没错,这里可以看出进程之间数据是相互独立的,子进程修改的全局变量也只是自己的,最后打印出来的仍然是没有修改过的x的值
xxxxxxxxxx
41
1
# 进程和线程之间开启速度的比较
2
# 通过在进程和线程的.start()上下分别加上起止时间来取到开启时间
3
from threading import Thread
4
from multiprocessing import Process
5
import time
6
7
8
def task(name):
9
print(f'{name} start')
10
time.sleep(2)
11
print(f'{name} end')
12
13
14
if __name__ == '__main__':
15
t = Thread(target=task, args=('nick',))
16
p = Process(target=task, args=('rocky',))
17
18
start_t = time.time()
19
t.start()
20
end_t = time.time()
21
22
start_p = time.time()
23
p.start()
24
end_p = time.time()
25
26
print(f'线程开启耗时{end_t - start_t:9.9f}')
27
print(f'进程开启耗时{end_p - start_p:9.9f}')
28
print('主')
29
30
'''
31
nick start
32
线程开启耗时0.000997305
33
进程开启耗时0.003051519
34
主
35
rocky start
36
nick end
37
rocky end
38
39
40
以上可以看出线程的开启速度比进程快很多,基本不是一个数量级
41
'''
线程的一些其他用法
线程有很多和进程比较相似的用法,比如start,比如join等等,以下介绍几种常用的线程的方法.
-
线程的join
xxxxxxxxxx
101def task():
2print('子线程 start')3time.sleep(2)
4print('子线程 end')5
6
7t = Thread(target=task)
8t.start()
9t.join() # 等待子线程运行结束,但并没有调用wait,也不用回收pid号,这点和之前进程里所用的join不同
10print('主线程') -
线程的其他用法
- is_alive:判断进程是否还存在
- getName:取到名字,默认Thread-1,数字依次排序
- setName:赋予名字
- currentThread:返回当前的线程变量
- enumerate:返回一个包含正在运行的线程的list,正在运行的线程启动后,结束前,不包括启动前和终止后的线程
- activeCount:返回正在运行的线程数量,与len(enumerate)一个意思
xxxxxxxxxx
26
1
from threading import Thread
2
import threading
3
import time
4
import os
5
6
7
def task():
8
print('线程 start')
9
# time.sleep(2)
10
# print(f'线程中{t.is_alive()}')
11
print('线程 end')
12
# print(threading.currentThread())
13
print(threading.enumerate())
14
15
16
if __name__ == '__main__':
17
t = Thread(target=task)
18
t1 = Thread(target=task)
19
t.start()
20
t1.start()
21
t.join()
22
t1.join()
23
# print(f'进程中{t.is_alive()}')
24
# print(t.getName())
25
# print('主')
26
print(threading.enumerate())
-
守护线程
守护的是整个进程的运行周期,即进程里面有别的线程没有运行完,守护线程就不会结束(除非自己的代码已经运行完了),但如果该进程以及其下面除了守护线程之外的所有线程都结束了,那么守护线程也会随之结束(即便自己的代码 没有运行完,也会直接结束).
xxxxxxxxxx
1251from threading import Thread,enumerate
2import time
3
4
5def task():
6print('守护线程 start')7time.sleep(7)
8print('守护线程 end')9
10
11def task2():
12print('子线程 start')13time.sleep(5)
14# print(enumerate())
15print('子护线程 end')16
17
18if __name__ == '__main__':
19t = Thread(target=task)
20t1 = Thread(target=task2)
21t.daemon = True
22t1.start()
23t.start()
24print('主进程')25