再谈多任务编程

多任务是指在同一时间内执行多个任务,例如: 现在电脑安装的操作系统都是多任务操作系统,可以同时运行着多个软件。例如:我们可以编写代码边听音乐边下载东西。

关于多任务编程我们其实之前也讲过**python中的进程**,我们这里再来回顾下。

多任务的执行方式

使用多任务就能充分利用CPU资源,提高程序的执行效率,让你的程序具备处理多个任务的能力;多任务执行方式有两种方式:并发并行,这里并行才是多个任务真正意义一起执行。

并发

在一段时间内交替去执行任务。即任务数大于核心数的时候

例如:

对于单核cpu处理多任务,操作系统轮流让各个软件交替执行,假如:软件1执行0.01秒,切换到软件2,软件2执行0.01秒,再切换到软件3,执行0.01秒……这样反复执行下去。表面上看,每个软件都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像这些软件都在同时执行一样,这里需要注意单核cpu是并发的执行多任务的。

并行

对于多核cpu处理多任务,操作系统会给cpu的每个内核安排一个执行的软件,多个内核是真正的一起执行软件。这里需要注意多核cpu是并行的执行多任务,始终有多个软件一起执行即任务数小于等于核心数的时候

进程

在Python程序中,想要实现多任务可以使用进程来完成,进程是实现多任务的一种方式。

一个正在运行的程序或者软件就是一个进程,它是操作系统进行资源分配的基本单位,也就是说每启动一个进程,操作系统都会给其分配一定的运行资源(内存资源)保证进程的运行。

比如:现实生活中的公司可以理解成是一个进程,公司提供办公资源(电脑、办公桌椅等),真正干活的是员工,员工可以理解成线程。

注意:

一个程序运行后至少有一个进程,一个进程默认有一个线程,进程里面可以创建多个线程,线程是依附在进程里面的,没有进程就没有线程

多进程可以完成多任务,每个进程就好比一家独立的公司,每个公司都各自在运营,每个进程也各自在运行,执行各自的任务。

进程是操作系统进行资源分配的基本单位进程是Python程序中实现多任务的一种方式

多进程的使用

导入进程包
1
2
# 导入进程包
import multiprocessing
Process进程类的说明

Process([group [, target [, name [, args [, kwargs]]]]])

  • group:指定进程组,目前只能使用None
  • target:执行的目标任务名
  • name:进程名字
  • args:以元组方式给执行任务传参
  • kwargs:以字典方式给执行任务传参

Process创建的实例对象的常用方法:

  • start():启动子进程实例(创建子进程)
  • join():等待子进程执行结束
  • terminate():不管任务是否完成,立即终止子进程

Process创建的实例对象的常用属性:

name:当前进程的别名,默认为Process-N,N为从1开始递增的整数

多进程任务代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020/11/16 16:36
# @Author : 托小尼
# @Email : 646547989@qq.com
# @URI : https://www.diandian100.cn
# @File : process.py
# @desc : 多进程
import multiprocessing
import time


def audio():
for i in range(3):
print('播放声音')
time.sleep(2)


def show():
for i in range(3):
print('播放画面')
time.sleep(2)


if __name__ == '__main__':
sub_process = multiprocessing.Process(target=audio)
show_process = multiprocessing.Process(target=show)
show_process.start()
sub_process.start()

运行结果

1
2
3
4
5
6
显示画面
播放声音
播放声音
显示画面
显示画面
播放声音
获取进程编号

os.getpid() 表示获取当前进程编号;os.getppid() 表示获取当前父进程编号。并且我们使用了进程传参的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020/11/16 16:36
# @Author : 托小尼
# @Email : 646547989@qq.com
# @URI : https://www.diandian100.cn
# @File : process.py
# @desc : 多进程
import multiprocessing
import os
import time


def audio(name="声音"):
for i in range(3):
print('播放%s,进程编号:%s,父进程编号:%s,当前进程:%s' % (name, os.getpid(), os.getppid(), multiprocessing.current_process()))
time.sleep(2)


def show(name="画面"):
for i in range(3):
print('播放%s,进程编号:%s,父进程编号:%s,当前进程:%s' % (name, os.getpid(), os.getppid(), multiprocessing.current_process()))
time.sleep(2)


if __name__ == '__main__':
sub_process = multiprocessing.Process(target=audio, name='python_sub111', args=('熊出没', ))
show_process = multiprocessing.Process(target=show, name='python_show', kwargs={"name":"奥特曼"})
show_process.start()
sub_process.start()
print('当前主进程的进程号码:%s,父进程编号:%s,当前进程:%s' % (os.getpid(), os.getppid(), multiprocessing.current_process()))

运行结果:

1
2
3
4
5
6
7
当前主进程的进程号码:2771,父进程编号:930,当前进程:<_MainProcess name='MainProcess' parent=None started>
播放奥特曼,进程编号:2773,父进程编号:2771,当前进程:<Process name='python_show' parent=2771 started>
播放熊出没,进程编号:2774,父进程编号:2771,当前进程:<Process name='python_sub111' parent=2771 started>
播放奥特曼,进程编号:2773,父进程编号:2771,当前进程:<Process name='python_show' parent=2771 started>
播放熊出没,进程编号:2774,父进程编号:2771,当前进程:<Process name='python_sub111' parent=2771 started>
播放奥特曼,进程编号:2773,父进程编号:2771,当前进程:<Process name='python_show' parent=2771 started>
播放熊出没,进程编号:2774,父进程编号:2771,当前进程:<Process name='python_sub111' parent=2771 started>

注:我们发现主进程也有父进程,原因是我们使用pycharm执行的代码或者bash控制台执行的代码,父进程就是pycharm或bash控制台

进程总结
进程之间不共享全局变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import multiprocessing
import os
import time


lis = ['哈哈']


def audio(name="声音"):
for i in range(3):
# 为列表添加数据
lis.append(name+str(i))
print('播放%s,lis数据为:%s,进程编号:%s,父进程编号:%s,当前进程:%s' % (name, lis, os.getpid(), os.getppid(), multiprocessing.current_process()))
time.sleep(2)


def show(name="画面"):
for i in range(3):
print('播放%s,进程编号:%s,lis数据为:%s,父进程编号:%s,当前进程:%s' % (name, lis, os.getpid(), os.getppid(), multiprocessing.current_process()))
time.sleep(2)


if __name__ == '__main__':
sub_process = multiprocessing.Process(target=audio, name='python_sub111', args=('熊出没', ))
show_process = multiprocessing.Process(target=show, name='python_show', kwargs={"name":"奥特曼"})
show_process.start()
sub_process.start()
print('lis数据为:%s,当前主进程的进程号码:%s,父进程编号:%s,当前进程:%s' % (lis, os.getpid(), os.getppid(), multiprocessing.current_process()))

运行结果

1
2
3
4
5
6
7
lis数据为:['哈哈'],当前主进程的进程号码:2842,父进程编号:930,当前进程:<_MainProcess name='MainProcess' parent=None started>
播放熊出没,lis数据为:['哈哈', '熊出没0'],进程编号:2845,父进程编号:2842,当前进程:<Process name='python_sub111' parent=2842 started>
播放奥特曼,进程编号:['哈哈'],lis数据为:2844,父进程编号:2842,当前进程:<Process name='python_show' parent=2842 started>
播放奥特曼,进程编号:['哈哈'],lis数据为:2844,父进程编号:2842,当前进程:<Process name='python_show' parent=2842 started>
播放熊出没,lis数据为:['哈哈', '熊出没0', '熊出没1'],进程编号:2845,父进程编号:2842,当前进程:<Process name='python_sub111' parent=2842 started>
播放奥特曼,进程编号:['哈哈'],lis数据为:2844,父进程编号:2842,当前进程:<Process name='python_show' parent=2842 started>
播放熊出没,lis数据为:['哈哈', '熊出没0', '熊出没1', '熊出没2'],进程编号:2845,父进程编号:2842,当前进程:<Process name='python_sub111' parent=2842 started>

从结果我们发现,在audio里修改的lis数据,show进程和主进程中lis都没有发生改变。

创建子进程会对主进程资源进行拷贝,也就是说子进程是主进程的一个副本,好比是一对双胞胎,之所以进程之间不共享全局变量,是因为操作的不是同一个进程里面的全局变量,只不过不同进程里面的全局变量名字相同而已。

主进程会等待所有的子进程执行结束再结束

其实从上面代码就可以看出来我们主进程就一个打印,早都执行完了,剩下子进程在执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020/11/16 16:36
# @Author : 托小尼
# @Email : 646547989@qq.com
# @URI : https://www.diandian100.cn
# @File : process.py
# @desc : 多进程
import multiprocessing
import os
import time


lis = ['哈哈']


def audio(name="声音"):
for i in range(3):
# 为列表添加数据
lis.append(name+str(i))
print('播放%s,lis数据为:%s,进程编号:%s,父进程编号:%s,当前进程:%s' % (name, lis, os.getpid(), os.getppid(), multiprocessing.current_process()))
time.sleep(2)


def show(name="画面"):
for i in range(3):
print('播放%s,进程编号:%s,lis数据为:%s,父进程编号:%s,当前进程:%s' % (name, lis, os.getpid(), os.getppid(), multiprocessing.current_process()))
time.sleep(2)


if __name__ == '__main__':
sub_process = multiprocessing.Process(target=audio, name='python_sub111', args=('熊出没', ))
show_process = multiprocessing.Process(target=show, name='python_show', kwargs={"name":"奥特曼"})
# 设置守护主进程,主进程退出子进程直接销毁,子进程的生命周期依赖与主进程
show_process.daemon = True
show_process.start()
sub_process.start()
print('lis数据为:%s,当前主进程的进程号码:%s,父进程编号:%s,当前进程:%s' % (lis, os.getpid(), os.getppid(), multiprocessing.current_process()))

我们设置了守护主进程,朱景恒执行完并没有等待奥特曼进程运行完就直接结束了。

运行结果:

1
2
3
4
5
6
lis数据为:['哈哈'],当前主进程的进程号码:2865,父进程编号:930,当前进程:<_MainProcess name='MainProcess' parent=None started>
播放熊出没,lis数据为:['哈哈', '熊出没0'],进程编号:2868,父进程编号:2865,当前进程:<Process name='python_sub111' parent=2865 started>
播放熊出没,lis数据为:['哈哈', '熊出没0', '熊出没1'],进程编号:2868,父进程编号:2865,当前进程:<Process name='python_sub111' parent=2865 started>
播放熊出没,lis数据为:['哈哈', '熊出没0', '熊出没1', '熊出没2'],进程编号:2868,父进程编号:2865,当前进程:<Process name='python_sub111' parent=2865 started>

Process finished with exit code 0
  • 为了保证子进程能够正常的运行,主进程会等所有的子进程执行完成以后再销毁,设置守护主进程的目的是主进程退出子进程销毁,不让主进程再等待子进程去执行
  • 设置守护主进程方式: 子进程对象.daemon = True
  • 销毁子进程方式: 子进程对象.terminate()

线程

线程是进程中执行代码的一个分支,每个执行分支(线程)要想工作执行代码需要cpu进行调度 ,也就是说线程是cpu调度的基本单位,每个进程至少都有一个线程,而这个线程就是我们通常说的主线程。线程是Python程序中实现多任务的另外一种方式,线程的执行需要cpu调度来完成。

多线程使用

导入线程模块
1
2
#导入线程模块
import threading
线程类Thread参数说明

Thread([group [, target [, name [, args [, kwargs]]]]])

  • group: 线程组,目前只能使用None
  • target: 执行的目标任务名
  • args: 以元组的方式给执行任务传参
  • kwargs: 以字典方式给执行任务传参
  • name: 线程名,一般不用设置
启动线程

启动线程使用start方法

多线程完成多任务的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import threading
import time

# 唱歌任务
def sing():
# 扩展: 获取当前线程
# print("sing当前执行的线程为:", threading.current_thread())
for i in range(3):
print("正在唱歌...%d" % i)
time.sleep(1)

# 跳舞任务
def dance():
# 扩展: 获取当前线程
# print("dance当前执行的线程为:", threading.current_thread())
for i in range(3):
print("正在跳舞...%d" % i)
time.sleep(1)


if __name__ == '__main__':
# 扩展: 获取当前线程
# print("当前执行的线程为:", threading.current_thread())
# 创建唱歌的线程
# target: 线程执行的函数名
sing_thread = threading.Thread(target=sing)

# 创建跳舞的线程
dance_thread = threading.Thread(target=dance)

# 开启线程
sing_thread.start()
dance_thread.start()

执行结果:

1
2
3
4
5
6
正在唱歌...0
正在跳舞...0
正在唱歌...1
正在跳舞...1
正在唱歌...2
正在跳舞...2

传参和数据共享

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020/11/17 14:48
# @Author : 托小尼
# @Email : 646547989@qq.com
# @URI : https://www.diandian100.cn
# @File : thred.py
# @Desc : 多进程
import os
import threading
import time

px = [33, ]


def run(device):
for i in range(3):
print('当前进程:%s, 当前线程:%s、%s运行第%s次……' % (os.getpid(), threading.current_thread(), device, i))
px.append(device+str(i))
time.sleep(1)


def play(software):
for i in range(3):
print('play线程中获取到的:%s,当前进程:%s, 当前线程:%s、%s开始播放第%s次……' % (px, os.getpid(), threading.current_thread(), software, i))
time.sleep(1)


if __name__ == '__main__':
run_thred = threading.Thread(target=run, name='run_thred', args=('显示器',))
play_thred = threading.Thread(target=play, name='play_thred', kwargs={'software': '爱奇艺'})
play_thred.start()
run_thred.start()
print('当前线程:%s, 当前进程:%s' % (threading.current_thread(), os.getpid()))
print('主线程获取到:', px)

运行结果

1
2
3
4
5
6
7
8
9
10
play线程中获取到的:[33],当前进程:2932, 当前线程:<Thread(play_thred, started 123145556488192)>、爱奇艺开始播放第0次……
当前进程:2932, 当前线程:<Thread(run_thred, started 123145573277696)>、显示器运行第0次……
当前线程:<_MainThread(MainThread, started 140735730156416)>, 当前进程:2932
主线程获取到: [33, '显示器0']
play线程中获取到的:[33, '显示器0'],当前进程:2932, 当前线程:<Thread(play_thred, started 123145556488192)>、爱奇艺开始播放第1次……
当前进程:2932, 当前线程:<Thread(run_thred, started 123145573277696)>、显示器运行第1次……
play线程中获取到的:[33, '显示器0', '显示器1'],当前进程:2932, 当前线程:<Thread(play_thred, started 123145556488192)>、爱奇艺开始播放第2次……
当前进程:2932, 当前线程:<Thread(run_thred, started 123145573277696)>、显示器运行第2次……

Process finished with exit code 0

线程的传参和进程类似,另外从代码中我们可以看出线程的特点,无需,数据共享。

线程之间执行是无序的,它是由cpu调度决定的 ,cpu调度哪个线程,哪个线程就先执行,没有调度的线程不能执行。

进程之间执行也是无序的,它是由操作系统调度决定的,操作系统调度哪个进程,哪个进程就先执行,没有调度的进程不能执行。

主线程会等待所有的子线程执行结束再结束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020/11/17 14:48
# @Author : 托小尼
# @Email : 646547989@qq.com
# @URI : https://www.diandian100.cn
# @File : thred.py
# @Desc : 多进程
import os
import threading
import time

px = [33, ]


def play(software):
for i in range(3):
print('play线程中获取到的:%s,当前进程:%s, 当前线程:%s、%s开始播放第%s次……' % (px, os.getpid(), threading.current_thread(), software, i))
time.sleep(1)


if __name__ == '__main__':
play_thred = threading.Thread(target=play, name='play_thred', kwargs={'software': '爱奇艺'})
# 设置主线程守护
play_thred.setDaemon(True)
play_thred.start()
print('当前线程:%s, 当前进程:%s' % (threading.current_thread(), os.getpid()))
print('主线程获取到:', px)

运行结果:

1
2
3
4
play线程中获取到的:[33],当前进程:2953, 当前线程:<Thread(play_thred, started daemon 123145537007616)>、爱奇艺开始播放第0次……当前线程:<_MainThread(MainThread, started 140735730156416)>, 当前进程:2953
主线程获取到: [33]

Process finished with exit code 0

守护线程有两种方式:

  1. threading.Thread(target=play, daemon=True)
  2. 线程对象.setDaemon(True)

线程之间共享全局变量数据出现错误问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020/11/17 14:48
# @Author : 托小尼
# @Email : 646547989@qq.com
# @URI : https://www.diandian100.cn
# @File : thred.py
# @Desc : 多进程
import os
import threading
import time


total = 0


def sum1():
global total
for i in range(1000000):
total += 1
print("sum1结果:", total)


def sum2():
global total
for i in range(1000000):
total += 1
print("sum2结果:", total)


if __name__ == '__main__':

sum1_thread = threading.Thread(target=sum1)
sum2_thread = threading.Thread(target=sum2)
sum1_thread.start()
sum2_thread.start()
print("主线程结果:", total)


运行结果

1
2
3
4
5
主线程结果: 169365
sum1结果: 1232397
sum2结果: 1365596

Process finished with exit code 0

多线程同时对全局变量操作数据发生了错误

错误分析:

两个线程sum1_thread和sum2_thread都要对全局变量total(默认是0)进行加1运算,但是由于是多线程同时操作,有可能出现下面情况:

  1. 在total=0时,sum1_thread取得total=0。此时系统把sum1_thread调度为”sleeping”状态,把sum2_thread转换为”running”状态,t2也获得total=0
  2. 然后sum2_thread对得到的值进行加1并赋给total,使得total=1
  3. 然后系统又把sum2_thread调度为”sleeping”,把sum1_thread转为”running”。线程t1又把它之前得到的0加1后赋值给total。
  4. 这样导致虽然sum1_thread和sum2_thread都对total加1,但结果仍然是total=1

全局变量数据错误的解决办法:

线程同步: 保证同一时刻只能有一个线程去操作全局变量 同步: 就是协同步调,按预定的先后次序进行运行。如:你说完,我再说, 好比现实生活中的对讲机

解决方案
线程等待(join)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020/11/17 14:48
# @Author : 托小尼
# @Email : 646547989@qq.com
# @URI : https://www.diandian100.cn
# @File : thred.py
# @Desc : 多进程
import os
import threading
import time


total = 0


def sum1():
global total
for i in range(1000000):
total += 1
print("sum1结果:", total)


def sum2():
global total
for i in range(1000000):
total += 1
print("sum2结果:", total)


if __name__ == '__main__':
sum1_thread = threading.Thread(target=sum1)
sum2_thread = threading.Thread(target=sum2)
sum1_thread.start()
sum1_thread.join()
sum2_thread.start()

结果:

1
2
3
4
sum1结果: 1000000
sum2结果: 2000000

Process finished with exit code 0
互斥锁

互斥锁: 对共享数据进行锁定,保证同一时刻只能有一个线程去操作。

注意:

  • 互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程需要等待,等互斥锁使用完释放后,其它等待的线程再去抢这个锁。

threading模块中定义了Lock变量,这个变量本质上是一个函数,通过调用这个函数可以获取一把互斥锁。

互斥锁使用步骤:

1
2
3
4
5
6
7
8
9
10
# 创建锁
lock = threading.Lock()

# 上锁
lock.acquire()

...这里编写代码能保证同一时刻只能有一个线程去操作, 对共享数据进行锁定...

# 释放锁
lock.release()

acquire和release方法之间的代码同一时刻只能有一个线程去操作
如果在调用acquire方法的时候 其他线程已经使用了这个互斥锁,那么此时acquire方法会堵塞,直到这个互斥锁释放后才能再次上锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020/11/17 14:48
# @Author : 托小尼
# @Email : 646547989@qq.com
# @URI : https://www.diandian100.cn
# @File : thred.py
# @Desc : 多进程
import os
import threading
import time


total = 0
lock = threading.Lock()


def sum1():
lock.acquire()
global total
for i in range(1000000):
total += 1
print("sum1结果:", total)
lock.release()


def sum2():
lock.acquire()
global total
for i in range(1000000):
total += 1
print("sum2结果:", total)
lock.release()


if __name__ == '__main__':
sum1_thread = threading.Thread(target=sum1)
sum2_thread = threading.Thread(target=sum2)
sum1_thread.start()
sum2_thread.start()

结果:

1
2
3
4
sum1结果: 1000000
sum2结果: 2000000

Process finished with exit code 0

死锁

死锁: 一直等待对方释放锁的情景就是死锁

死锁的结果

会造成应用程序的停止响应,不能再处理其它任务了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import threading
import time

# 创建互斥锁
lock = threading.Lock()


# 根据下标去取值, 保证同一时刻只能有一个线程去取值
def get_value(index):

# 上锁
lock.acquire()
print(threading.current_thread())
my_list = [3,6,8,1]
# 判断下标释放越界
if index >= len(my_list):
print("下标越界:", index)
return
value = my_list[index]
print(value)
time.sleep(0.2)
# 释放锁
lock.release()


if __name__ == '__main__':
# 模拟大量线程去执行取值操作
for i in range(30):
sub_thread = threading.Thread(target=get_value, args=(i,))
sub_thread.start()

运行结果:

1
2
3
4
5
6
7
8
9
10
11
<Thread(Thread-1, started 123145487867904)>
3
<Thread(Thread-2, started 123145504657408)>
6
<Thread(Thread-3, started 123145521446912)>
8
<Thread(Thread-4, started 123145538236416)>
1
<Thread(Thread-5, started 123145555025920)>
下标越界: 4

我们发现程序停在此处了,下面代码显示了如何避免死锁的发生

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import threading
import time

# 创建互斥锁
lock = threading.Lock()


# 根据下标去取值, 保证同一时刻只能有一个线程去取值
def get_value(index):

# 上锁
lock.acquire()
print(threading.current_thread())
my_list = [3,6,8,1]
if index >= len(my_list):
print("下标越界:", index)
# 当下标越界需要释放锁,让后面的线程还可以取值
lock.release()
return
value = my_list[index]
print(value)
time.sleep(0.2)
# 释放锁
lock.release()


if __name__ == '__main__':
# 模拟大量线程去执行取值操作
for i in range(30):
sub_thread = threading.Thread(target=get_value, args=(i,))
sub_thread.start()

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<Thread(Thread-1, started 123145410772992)>
3
<Thread(Thread-2, started 123145427562496)>
6
<Thread(Thread-3, started 123145444352000)>
8
<Thread(Thread-4, started 123145461141504)>
1
<Thread(Thread-5, started 123145477931008)>
下标越界: 4
<Thread(Thread-6, started 123145494720512)>
下标越界: 5
<Thread(Thread-7, started 123145511510016)>
下标越界: 6
<Thread(Thread-8, started 123145528299520)>
下标越界: 7

Process finished with exit code 0

线程进程总结

关系对比

  1. 线程是依附在进程里面的,没有进程就没有线程。
  2. 一个进程默认提供一条线程,进程可以创建多个线程。

区别对比

  1. 进程之间不共享全局变量
  2. 线程之间共享全局变量,但是要注意资源竞争的问题,解决办法: 互斥锁或者线程同步
  3. 创建进程的资源开销要比创建线程的资源开销要大
  4. 进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
  5. 线程不能够独立执行,必须依存在进程中
  6. 多进程开发比单进程多线程开发稳定性要强

优缺点对比

  • 进程优缺点:
    • 优点:可以用多核
    • 缺点:资源开销大
  • 线程优缺点:
    • 优点:资源开销小
    • 缺点:不能使用多核

总结

  • 进程和线程都是完成多任务的一种方式
  • 多进程要比多线程消耗的资源多,但是多进程开发比单进程多线程开发稳定性要强,某个进程挂掉不会影响其它进程。
  • 多进程可以使用cpu的多核运行,多线程可以共享全局变量。
  • 线程不能单独执行必须依附在进程里面
  • 应用场景:计算密集性操作使用多进程;读写等io操作使用多线程。