9. 进程和线程

1. 进程

1. 进程

  • 并发:在一段时间内交替的去执行任务
    • 对于单个CPU处理多任务,操作系统会轮流让各个软件交替的执行。
  • 并行:对于多核的CPU处理多任务,操作系统会给CPU每个内核安排一个执行的软件,多个内核是真正的一起执行软件。
    • 这里需要注意的是多核的CPU是并行的执行任务,始终有多个软件一起执行。

1.1 进程的概念

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

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

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

1.2 进程的作用

一个程序可以创建一至多个进程,一个进程可以创建一至多个线程,每个线程处理一个任务。

2. 多进程的使用

2.1 导入进程包

# 导入进程包
import multiprocessing

2.2 Process进程类的说明

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

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

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

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

3 多进程实例

3.1 多进程完成多任务的代码

# 1. 导入进程包
import multiprocessing
import time


# 1.1 创建dance的任务
def dance():
    for i in range(3):
        print("dancing...")
        time.sleep(0.2)


def music():
    for i in range(3):
        print("music...")
        time.sleep(0.2)


# 2. 创建子进程(自己手动创建的进程称为子进程,在__init__.py文件中已经导入的Process类)
# 2.1 group:进程组,目前只能使用None,一般不需要设置
#     target:进程执行的目标任务
#     name:进程名,如果不设置,默认是process-1
dance_process = multiprocessing.Process(target=dance)

# 再创建一个子进程,执行music
music_process = multiprocessing.Process(target=music)
if __name__ == '__main__':
    # 3. 启动进程执行对应的任务
    dance_process.start()
    # 主进程执行唱歌任务,直接调用的话是使用主进程执行
    # music()
    
    # 进程执行是无序的,具体那个进程先执行有操作系统调度决定的
    music_process.start()

3.2 获取当前父进程的编号

  • os.getpid() 获取当前的进程编号

  • os.getppid() 表示获取当前父进程的编号

  • os.kill(id,type) 杀死指定的进程编号 id是只要杀死的进程编号,type是值kill的类型,强制杀死 kill -9,type=9

#!/usr/bin/python3
# coding=utf-8

# 1. 导入进程包
import multiprocessing
import time
import os


# 跳舞任务
def dance():
    # 获取当前进程(子进程)的编号
    dance_process_id = os.getpid()
    # 获取当前进程对象,查看当前代码是由哪个进程执行的 : multiprocessing.current_process()
    print("dance_process_id:", dance_process_id, multiprocessing.current_process())
    # 获取当前进程的父进程编号
    dance_process_parent_id = os.getppid()
    print("dance_process的父进程编号是:", dance_process_parent_id)

    for i in range(3):
        print("跳舞中...")
        time.sleep(0.2)
        # 扩展: 根据进程编号强制杀死指定进程
        os.kill(dance_process_id, 9)


# 唱歌任务
def sing():
    # 获取当前进程(子进程)的编号
    sing_process_id = os.getpid()
    # 获取当前进程对象,查看当前代码是由那个进程执行的 : multiprocessing.current_process()
    print("sing_process_id:", sing_process_id, multiprocessing.current_process())

    # 获取当前进程的父进程编号
    sing_process_parent_id = os.getppid()
    print("sing_process的父进程编号是:", sing_process_parent_id)

    for i in range(3):
        print("唱歌中...")
        time.sleep(0.2)


# 获取当前进程(主进程)的编号
main_process_id = os.getpid()
# 获取当前进程对象,查看当前代码是由那个进程执行的 : multiprocessing.current_process()
print("main_process_id:", main_process_id, multiprocessing.current_process())

# 2. 创建子进程(自己手动创建的进程称为子进程, 在__init__.py文件中已经导入的Process类)
# 1. group: 进程组,目前只能使用None,一般不需要设置
# 2. target: 进程执行的目标任务
# 3. name: 进程名,如果不设置,默认是Process-1, ......
dance_process = multiprocessing.Process(target=dance, name="dance_process")
print("dance_process:", dance_process)
sing_process = multiprocessing.Process(target=sing, name="sing_process")
print("sing_process:", sing_process)

# 3. 启动进程执行对应的任务
dance_process.start()
sing_process.start()

# 进程执行是无序的,具体那个进程先执行是由操作系统调度决定

sudo+ssh://root@192.168.211.129:22/usr/bin/python3 -u /tmp/pycharm_project_819/day07/02.获取进程编号.py
main_process_id: 2740 <_MainProcess name='MainProcess' parent=None started>
dance_process: <Process name='dance_process' parent=2740 initial>
sing_process: <Process name='sing_process' parent=2740 initial>
dance_process_id: 2741 <Process name='dance_process' parent=2740 started>
dance_process的父进程编号是: 2740
跳舞中...
sing_process_id: 2742 <Process name='sing_process' parent=2740 started>
sing_process的父进程编号是: 2740
唱歌中...
唱歌中...
唱歌中...

Process finished with exit code 0

注意:**process([group [,target [,name [,args [, kwargs]]]]])**中target参数后边应该跟的是名字,不加小括号。加上小括号是调用函数,默认为None。必须指定target

3.3 进程执行带有参数的任务

上面使用的进程执行的任务是没有参数的,假如使用进程执行的任务是带有参数的,传参方式如下:

Process类执行任务并给任务传参的方式有两种:

  • args 表示以元组的方式给执行任务传参
  • kwargs 表示以字典方式给执行任务传参
3.3.1 args和kwargs参数的使用
#!/usr/bin/python3
# coding=utf-8

import multiprocessing


# 显示信息的任务
def show_info(name, age):
    print(name, age)


# 创建子进程
# 以元组方式传参,元组里面的元素顺序要和函数的参数顺序保持一致
# sub_process = multiprocessing.Process(target=show_info, args=("李四", 20))
# # 启动进程
# sub_process.start()

# 以字典方式传参,字典里面的key要和函数里面的参数名保持一致,没有顺序要求
# sub_process = multiprocessing.Process(target=show_info, kwargs={"age":20, "name": '王五'})
# # 启动进程
# sub_process.start()

# sub_process = multiprocessing.Process(target=show_info, args=("冯七",), kwargs={"age": 20})
# 启动进程
sub_process.start()
sudo+ssh://root@192.168.211.129:22/usr/bin/python3 -u /tmp/pycharm_project_819/day07/03.args和kwargs参数的使用.py
王五 20

Process finished with exit code 0

# 以上三种方式对args传参效果是一样的
  • args 以元组方式传参,元组里面的元素顺序要和函数的参数顺序保持一致

  • kwargs 以字典方式传参,字典里面的key要和函数里面的参数名保持一致,没有顺序要求

args和kwargs可以搭配使用

3.4 进程之间不共享全局变量

#!/usr/bin/python3
# coding=utf-8

import multiprocessing
import time

# 定义全局变量列表
g_list = list()


# 添加数据的任务
def add_data():
    for i in range(3):
        # 因为列表是可变类型,可以在原有内存的基础上修改数据,并且修改后内存地址不变,所以不需要加上global关键字
        # 加上global 表示声明要修改全局变量的内存地址
        g_list.append(i)
        print("add:", i)
        time.sleep(0.2)

    print("添加完成:", g_list)


# 读取数据的任务
def read_data():
    print("read:", g_list)



# 提示: 对应linux和mac主进程执行的代码不会进程拷贝,但是对应window系统来说主进程执行的代码也会进行拷贝执行
#         对应window来说创建子进程的代码如果进程拷贝执行相当于递归无限制进行创建子进程,会报错

# 如果解决windows递归创建子进程,通过判断是否是主模块来解决
# 理解说明: 直接执行的模块就是主模块,那么直接执行的模块里面就应该添加判断是否是主模块的代码
# 1. 防止别人导入文件的时候执行main里面的代码
# 2. 防止windows系统递归创建子进程
# if __name__ == '__main__':

# 添加数据的子进程
add_process = multiprocessing.Process(target=add_data)
# 读取数据的子进程
read_process = multiprocessing.Process(target=read_data)

# 启动进程执行对应的任务
add_process.start()
# 当前进程(主进程)等待添加数据的进程执行完成以后代码再继续往下执行
add_process.join()
print("main:", g_list)
read_process.start()

# 结论: 进程之间不共享全局变量

# 扩展:
# fork 函数, 创建子进程都需要对主进程的代码进行拷贝
# fork mac 和 linux python解释器里面都有fork函数,通过fork函数去创建的子进程
sudo+ssh://root@192.168.211.129:22/usr/bin/python3 -u /tmp/pycharm_project_819/day07/04.进程之间不共享全局变量.py
add: 0
add: 1
add: 2
添加完成: [0, 1, 2]
main: []
read: []

Process finished with exit code 0

注意:进程之间是不会共享全局变量的,就拿这个例子来讲。add_data和read_data是两个开启子进程进行调用后,add_data将数据全部添加到空的列表中,所以在g_list中数据应该是[0,1,2],而read_data进程中则是直接对g_list进行读取,读取后显示为空。直接打印全局变量后列表依然是空的。

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

import multiprocessing
import time


def task():
    # for i in range(10):
    while True:
        print("任务执行中...")
        time.sleep(0.2)


# 判断是否是直接执行的模块, 程序入口模块

# 标准python写法,直接执行的模块,需要加上判断是否是主模块的代码
if __name__ == '__main__':

    # 创建子进程
    sub_process = multiprocessing.Process(target=task)
    # 把子进程设置成为守护主进程,以后主进程退出子进程直接销毁
    # sub_process.daemon = True
    sub_process.start()

    # 主进程延时0.5秒钟
    time.sleep(0.5)
    # 退出主进程之前,先让子进程进行销毁
    sub_process.terminate()
    print("over")

# 结论: 主进程会等待子进程执行完成以后程序再退出

# 解决办法: 主进程退出子进程销毁
# 1. 让子进程设置成为守护主进程,主进程退出子进程销毁,子进程会依赖主进程
# 2. 让主进程退出之前先让子进程销毁

2. 线程

1. 线程

1.1 线程的介绍

在Python中,想要实现多任务除了使用进程,还可以使用线程来完成,线程是实现多任务的另外一种方式

1.2 线程的概念

线程是进程中执行代码的一个分支,每个执行分支(线程)要想工作执行代码需要cpu进行调度,也就是说线程是cpu调度的基本单位,每个进程至少都有一个线程,而这个线程就是我们通常说的主线程。

1.3 线程的作用

多线程可以完成多任务

说明:程序启动默认会有一个主线程,程序员自己创建的线程可以称之为子线程,多线程可以完成多任务

2. 多线程的使用

2.1 导入线程模块

# 导入线程模块

import threading

2.2 线程类Thread参数说明

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

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

2.3 启动线程

启动线程使用start方法

2.4 多线程完成多任务的代码

#!/usr/bin/python3
# coding=utf-8

# 1.导入线程模块
import threading
import time


def sing():
    # 获取当前线程
    current_thread = threading.current_thread()
    print("current_thread:",current_thread)
    for i in range(3):
        print("唱歌中...")
        time.sleep(0.2)

def dance():
    # 获取当前线程
    current_thread = threading.current_thread()
    print("current_thread:",current_thread)
    for i in range(3):
        print("跳舞中...")
        time.sleep(0.2)

if __name__ == '__main__':
    # 获取当前线程
    current_thread = threading.current_thread()
    print("main_thread:", current_thread)

    # 2.创建子线程
    sing_thread = threading.Thread(target=sing)
    dance_thread = threading.Thread(target=dance)
    # 3.启动子线程并执行相应任务
    sing_thread.start()
    dance_thread.start()


sudo+ssh://root@192.168.211.129:22/usr/bin/python3 -u /tmp/pycharm_project_819/day07/06.多线程的使用.py
main_thread: <_MainThread(MainThread, started 140403023632192)>
current_thread: <Thread(Thread-1, started 140402894415616)>
唱歌中...
current_thread: <Thread(Thread-2, started 140402816448256)>
跳舞中...
唱歌中...
跳舞中...
唱歌中...
跳舞中...

Process finished with exit code 0

2.5 线程执行带有参数的任务

具体实现其实和进程的方式是一样的,只是把mutiprocessing换成了threading

import threading


def show_info(name, age):
    print("name: %s age: %d" % (name, age))

if __name__ == '__main__':
    # 创建子线程
    # 以元组方式传参,要保证元组里面元素的顺序和函数的参数顺序一致
    # sub_thread = threading.Thread(target=show_info, args=("李四", 20))
    # # 启动线程执行对应的任务
    # sub_thread.start()

    # 以字典的方式传参,要保证字典里面的key和函数的参数名保持一致
    sub_thread = threading.Thread(target=show_info, kwargs={"name": "王五", "age": 30})
    # 启动线程执行对应的任务
    sub_thread.start()
sudo+ssh://root@192.168.211.129:22/usr/bin/python3 -u /tmp/pycharm_project_819/day07/07.线程执行带有参数的任务.py
name: 王五 age: 30

Process finished with exit code 0

2.6 线程的注意点

  • 线程之间执行是无序的
  • 主线程会等待所有的子线程执行结果再结束
  • 线程之间共享全局变量
  • 线程之间共享全局变量数据出现错误问题
2.6.1 线程之间是无序的
import threading
import time


def task():
    time.sleep(1)
    # 获取当前线程
    print(threading.current_thread())


if __name__ == '__main__':
    # 循环创建大量线程,测试线程之间执行是否无序
    for i in range(20):
        # 每循环一次创建一个子线程
        sub_thread = threading.Thread(target=task)
        # 启动子线程执行对应的任务
        sub_thread.start()

    # 线程之间执行是无序的,具体那个线程执行是由cpu调度决定的
sudo+ssh://root@192.168.211.129:22/usr/bin/python3 -u /tmp/pycharm_project_819/day07/08.线程之间执行是无序的.py
<Thread(Thread-1, started 140290919655168)>
<Thread(Thread-2, started 140290911262464)>
<Thread(Thread-3, started 140290902869760)>
<Thread(Thread-4, started 140290894477056)>
<Thread(Thread-6, started 140290677536512)>
<Thread(Thread-5, started 140290669143808)>
<Thread(Thread-7, started 140290660751104)>
<Thread(Thread-8, started 140290652358400)>
<Thread(Thread-9, started 140290643965696)>
<Thread(Thread-10, started 140290635572992)>
<Thread(Thread-11, started 140290627180288)>
<Thread(Thread-12, started 140290341992192)>
<Thread(Thread-13, started 140290333599488)>
<Thread(Thread-14, started 140290325206784)>
<Thread(Thread-15, started 140290316814080)>
<Thread(Thread-16, started 140290308421376)>
<Thread(Thread-17, started 140290300028672)>
<Thread(Thread-18, started 140290291635968)>
<Thread(Thread-19, started 140290207774464)>
<Thread(Thread-20, started 140290199381760)>

Process finished with exit code 0

进程执行也是无序的

import multiprocessing
import time


def task():
    time.sleep(1)
    # 获取当前线程
    print(multiprocessing.current_process())


if __name__ == '__main__':
    # 循环创建大量进程,测试进程之间执行是否无序
    for i in range(20):
        # 每循环一次创建一个子进程
        sub_process = multiprocessing.Process(target=task)
        # 启动子进程执行对应的任务
        sub_process.start()

    # 结论: 进程之间执行也是无序的,是由操作系统调度进程来决定的

sudo+ssh://root@192.168.211.129:22/usr/bin/python3 -u /tmp/pycharm_project_819/day07/09.进程之间执行是无序的.py
<Process name='Process-6' parent=3694 started>
<Process name='Process-2' parent=3694 started>
<Process name='Process-3' parent=3694 started>
<Process name='Process-4' parent=3694 started>
<Process name='Process-8' parent=3694 started>
<Process name='Process-9' parent=3694 started>
<Process name='Process-1' parent=3694 started>
<Process name='Process-13' parent=3694 started>
<Process name='Process-14' parent=3694 started>
<Process name='Process-11' parent=3694 started>
<Process name='Process-10' parent=3694 started>
<Process name='Process-5' parent=3694 started>
<Process name='Process-7' parent=3694 started>
<Process name='Process-15' parent=3694 started>
<Process name='Process-12' parent=3694 started>
<Process name='Process-18' parent=3694 started>
<Process name='Process-17' parent=3694 started>
<Process name='Process-16' parent=3694 started>
<Process name='Process-20' parent=3694 started>
<Process name='Process-19' parent=3694 started>

Process finished with exit code 0

2.6.2 主线程会等待所有的子线程执行结束再结束
import threading
import time


def task():
    while True:
        print("任务执行中...")
        time.sleep(0.3)

if __name__ == '__main__':
    # 创建子线程有两种方式
    # 方式一
    # daemon=True 表示创建的子线程守护主线程,主线程退出子线程直接销毁
    # sub_thread = threading.Thread(target=task, daemon=True)
    # 方式二
    sub_thread = threading.Thread(target=task)
    # 把子线程设置成为守护主线程
    sub_thread.setDaemon(True)
    sub_thread.start()

    # 主线程延时执行1秒
    time.sleep(1)

    print("over")
    # exit()

# 结论: 主线程会等待子线程执行结束再结束

# 解决办法: 把子线程设置成为守护主线程即可
sudo+ssh://root@192.168.211.129:22/usr/bin/python3 -u /tmp/pycharm_project_819/day07/10.主线程会等待所有的子线程执行结束再结束.py
任务执行中...
任务执行中...
任务执行中...
任务执行中...
over

Process finished with exit code 0

2.6.3 线程之间共享全局变量
import threading
import time


# 定义全局变量
g_list = []


# 添加数据的任务
def add_data():
    for i in range(3):
        # 每循环一次把数据添加到全局变量
        g_list.append(i)
        print("add:", i)
        time.sleep(0.3)

    # 代码执行到此,说明添加数据完成
    print("添加数据完成:", g_list)


# 读取数据的任务
def read_data():
    print("read:", g_list)


if __name__ == '__main__':
    # 创建添加数据的子线程
    add_thread = threading.Thread(target=add_data)
    # 创建读取数据的子线程
    read_thread = threading.Thread(target=read_data)

    # 启动线程执行对应的任务
    add_thread.start()
    # time.sleep(1)
    # 让当前线程(主线程)等待添加数据的子线程执行完成以后代码在继续执行
    add_thread.join()
    read_thread.start()
	print("main:",g_list)
# 结论: 线程之间共享全局变量
sudo+ssh://root@192.168.211.129:22/usr/bin/python3 -u /tmp/pycharm_project_819/day07/11.线程之间会共享全局变量.py
add: 0
add: 1
add: 2
添加数据完成: [0, 1, 2]
read: [0, 1, 2]
main: [0, 1, 2]

Process finished with exit code 0

因为多线程在同一个进程中,所以多线程可以共享全局变量

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

需求

  1. 定义两个函数,实现循环100万次,每循环一次给全局变量加1
  2. 创建两个子线程对应的两个函数,查看计算后的结果
import threading


# 全局变量
g_num = 0


# 循环100万次执行的任务
def task1():
    for i in range(1000000):
        # 每循环一次给全局变量加1
        global g_num  # 表示要声明修改全局变量的内存地址
        g_num = g_num + 1  # g_num += 1

    # 代码执行到此,说明数据计算完成
    print("task1:", g_num)


# 循环100万次执行的任务
def task2():
    for i in range(1000000):
        # 每循环一次给全局变量加1
        global g_num  # 表示要声明修改全局变量的内存地址
        g_num = g_num + 1  # g_num += 1

    # 代码执行到此,说明数据计算完成
    print("task2:", g_num)


if __name__ == '__main__':
    # 创建两个子线程
    first_thread = threading.Thread(target=task1)
    second_thread = threading.Thread(target=task2)

    # 启动线程执行任务
    first_thread.start()
    # 线程等待,让第一个线程先执行,然后在让第二个线程再执行,保证数据不会有问题
    # first_thread.join() # 主线程等待第一个子线程执行完成以后代码再继续往下执行
    second_thread.start()
sudo+ssh://root@192.168.211.129:22/usr/bin/python3 -u /tmp/pycharm_project_819/day07/12.线程之间共享全局变量数据错误的问题.py
task1: 1000000
task2: 1913647

Process finished with exit code 0

出现此种错误的原因是:在数据进行相加的过程中,线程1执行完后可能是线程2执行,但是在执行的过程中也可能出现线程1和线程2同时执行,导致缺少相加的环节。出现线程之间共享全局变量数据错误的问题。所以应当使用线程等待或者互斥锁处理此种问题。

线程等待使用 first_thread.join() # 主线程等待第一个子线程执行完成以后代码再继续往下执行

3. 互斥锁

3.1 互斥锁的概念

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

注意:

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

3.2 互斥锁的使用

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

互斥锁的使用步骤:

# 创建锁
mutex = threading.Lock()

# 上锁
mutex.acquire()

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

# 释放锁
mutex.release()

注意点:

  • acqiure和release方法之间的代码同一时刻只能有一个线程去操作
  • 如果在调用acquire方法的时候,其他线程已经使用了这个互斥锁,那么此时的acquire方法会堵塞,知道这个互斥锁释放后才能再次上锁。
3.2.1 使用互斥锁完成2个线程对同一个变量各加100万次的操作
import threading

# 全局变量
g_num = 0

# 创建互斥锁, Lock本质上是一个函数,通过调用函数可以创建一个互斥锁
lock = threading.Lock()

# 循环100万次执行的任务
def task1():
    # 上锁
    lock.acquire()
    for i in range(1000000):
        # 每循环一次给全局变量加1
        global g_num  # 表示要声明修改全局变量的内存地址
        g_num = g_num + 1  # g_num += 1

    # 代码执行到此,说明数据计算完成
    print("task1:", g_num)
    # 释放锁
    lock.release()


# 循环100万次执行的任务
def task2():
    # 上锁
    lock.acquire()
    for i in range(1000000):
        # 每循环一次给全局变量加1
        global g_num  # 表示要声明修改全局变量的内存地址
        g_num = g_num + 1  # g_num += 1

    # 代码执行到此,说明数据计算完成
    print("task2:", g_num)
    # 释放锁
    lock.release()


if __name__ == '__main__':
    # 创建两个子线程
    first_thread = threading.Thread(target=task1)
    second_thread = threading.Thread(target=task2)

    # 启动线程执行任务
    first_thread.start()

    second_thread.start()

    # 互斥锁可以保证同一时刻只有一个线程去执行代码,能够保证全局变量的数据没有问题
    # 线程等待和互斥锁都是把多任务改成单任务去执行,保证了数据的准确性,但是执行性能会下降
sudo+ssh://root@192.168.211.129:22/usr/bin/python3 -u /tmp/pycharm_project_819/day07/13.互斥锁.py
task1: 1000000
task2: 2000000

Process finished with exit code 0

  • 互斥锁可以保证同一时刻只有一个线程去执行代码,能够保证全局变量的数据没有问题

  • 线程等待和互斥锁都是把多任务改成单任务去执行,保证了数据的准确性,但是执行性能会下降

  • 互斥锁能够保证多个线程访问共享数据不会出现数据错误问题

3.3 小结

  • 互斥锁的作用就是保证同一时刻只能有一个线程去操作共享数据,保证共享数据不会出现错误问题
  • 使用互斥锁的好处确保某段关键代码只能由一个线程从头到尾的去执行
  • 使用互斥锁会影响代码的执行效率,多任务改成了单任务执行
  • 互斥锁如果没有使用好容易出现死锁的情况

4. 死锁

4.1 死锁的概念

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

死锁的结果:

  • 会造成应用程序的停止响应,不能再处理其他任务
# 死锁: 一直等待对方释放锁的情景叫做死锁
import threading


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


# 需求: 多线程同时根据下标在列表中取值,要保证同一时刻只能有一个线程去取值
def get_value(index):
    # 上锁
    lock.acquire()
    my_list = [1, 4, 6]
    # 判断下标是否越界
    if index >= len(my_list):
        print("下标越界:", index)
        # 取值不成功,也需要释放互斥锁,不要影响后面的线程去取值
        # 锁需要在合适的地方进行释放,防止死锁
        lock.release()
        return

    # 根据下标取值
    value = my_list[index]
    print(value)
    # 释放锁
    lock.release()


if __name__ == '__main__':
    # 创建大量线程,同时执行根据下标取值的任务
    for i in range(10):
        # 每循环一次创建一个子线程
        sub_thread = threading.Thread(target=get_value, args=(i,))
        # 启动线程执行任务
        sub_thread.start()
sudo+ssh://root@192.168.211.129:22/usr/bin/python3 -u /tmp/pycharm_project_819/day07/14.死锁.py
1
4
6
下标越界: 3
下标越界: 4
下标越界: 5
下标越界: 6
下标越界: 7
下标越界: 8
下标越界: 9

Process finished with exit code 0

3. 线程和进程的对比

3.1 关系对比

  • 线程是依附在进程里面的,没有进程就没有线程

  • 一个进程默认提供一条线程,进程可以创建多个线程

3.2 区别对比

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

3.3 优缺点对比

  • 进程优缺点:
    • 优点:可以用多核
    • 缺点:资源开销大
和计算密集型的相关操作使用多进程
  • 线程优缺点:
    • 优点:资源开销小
    • 缺点:不能是使用多核
文件写入,文件下载 I/O操作

3.4 小结

  • 继承和线程都是完成多任务的一种方式
  • 多进程要比多线程消耗的资源多,但是多进程的开发比单进程多线程开发稳定性要强,某个进程挂掉不会影响其他的进程
  • 多线程可以使用CPU的多核运行 ,多线程可以共享全局变量
  • 线程不能单独执行必须依附在进程里面
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

脑子是空的啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值