Python多任务之:进程与线程详解

2019-04-20  本文已影响0人  越大大雨天

进程与线程知识点详解

多任务

1.定义

2.执行方式

进程

1.定义

2.作用

3.编写步骤

  1. 导入进程包:import multiprocessing

  2. 创建子进程并指定执行的任务:

    • multiprocessing.Process(target=任务名)

    • 若需传参,参数传递形式分两种:

      • 元组方式传参(args): 元组方式传参一定要和参数的顺序保持一致:args=(参数1,)

      • 字典方式传参(kwargs): 字典方式传参字典中的key一定要和参数名保持一致:kwargs={"形参名":参数1}

  3. 启动进程执行任务

    • 进程名.start()
  4. 简单实例:

import multiprocessing

def fun_1(n):
    for i in range(n):
        print("这是一个子进程1")
        
def fun_2(n):
    for i in range(n):
        print("这是一个子进程2")
        
if __name__ == '__main__':
    process1 = multiprocessing.Process(target=fun_1,args=(5,))
    process2 = multiprocessing.Process(target=fun_2,args=(10,))
    process1.start()
    process2.start()

输出结果:

这是一个子进程1
这是一个子进程2
这是一个子进程1
这是一个子进程2
这是一个子进程1
这是一个子进程2
这是一个子进程1
这是一个子进程2
这是一个子进程1
这是一个子进程2
这是一个子进程2
这是一个子进程2
这是一个子进程2
这是一个子进程2
这是一个子进程2

4.进程之间不共享全局变量

import multiprocessing
import time

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

# 添加数据的任务
def add_data():
    for i in range(5):
        g_list.append(i)
        print("add:", i)
        time.sleep(0.2)

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

def read_data():
    print("read_data", g_list)

if __name__ == '__main__':
    # 创建添加数据的子进程
    add_data_process = multiprocessing.Process(target=add_data)
    # 创建读取数据的子进程
    read_data_process = multiprocessing.Process(target=read_data)

    # 启动子进程执行对应的任务
    add_data_process.start()
   
    # 主进程等待添加数据的子进程执行完成以后程序再继续往下执行,读取数据
    add_data_process.join()
    read_data_process.start()
    print("main:", g_list)

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

输出:

add: 0
add: 1
add: 2
add: 3
add: 4
add_data: [0, 1, 2, 3, 4]
main: []
read_data []

主进程及另一子进程的 g_list均为空

进程之间不共享全局变量的解释效果图:

image.png

5.获取进程编号

  1. 目的

    • 验证主进程和子进程的关系,得知子进程是由那个主进程创建出来的
  2. 获取方式

    先导入os模块

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

    • 获取当前父进程编号:os.getppid()

6.特别注意事项

7.其他

线程

1.概念

2.作用

3.多线程的使用步骤(与多进程基本相同)

  1. 导入线程模块:import threading

  2. 创建子线程并指定执行的任务

    • threading.Thread(target=任务名, args(,), kwargs{})

    • 若需传参,参数传递形式分两种:

      • 元组方式传参(args): 元组方式传参一定要和参数的顺序保持一致:args=(参数1,)

      • 字典方式传参(kwargs): 字典方式传参字典中的key一定要和参数名保持一致:kwargs={"形参名":参数1}

  3. 启动线程执行任务:线程名.start()

4.守护主线程

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

*假如我们想让主线程运行特定时间后同时销毁子线程,可以设置子线程守护:

设置主线程守护有两种方式:

  1. threading.Thread(target=show_info, daemon=True)

  2. 线程对象.setDaemon(True)

代码示例:

import threading
import time

# 测试主线程是否会等待子线程执行完成以后程序再退出
def show_info():
    for i in range(5):
        print("test:", i)
        time.sleep(0.5)

if __name__ == '__main__':
    # 创建子线程守护主线程 
    # daemon=True 守护主线程
    # 守护主线程方式1
    sub_thread = threading.Thread(target=show_info, daemon=True)
    # 设置成为守护主线程,主线程退出后子线程直接销毁不再执行子线程的代码
    # 守护主线程方式2
    # sub_thread.setDaemon(True)
    sub_thread.start()

    # 主线程延时1秒
    time.sleep(1)
    print("over")

执行结果:

test: 0
test: 1
over

特别注意:当有多个子线程时,必须设置每个子进程的daemon=True,才能实现主线程守护,这点不同于多进程中的情形,因为线程是依附于进程存在的.

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

示例代码:

import threading

# 定义全局变量
g_num = 0

# 循环一次给全局变量加1
def sum_num1():
    for i in range(1000000):
        global g_num
        g_num += 1

    print("sum1:", g_num)


# 循环一次给全局变量加1
def sum_num2():
    for i in range(1000000):
        global g_num
        g_num += 1
    print("sum2:", g_num)


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

    # 启动线程
    first_thread.start()
    # 启动线程
    second_thread.start()

执行结果:

sum1: 1210949
sum2: 1496035

可见数据结果明显发生了错误

错误分析:

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

  1. 在g_num=0时,first_thread取得g_num=0。此时系统把first_thread调度为”sleeping”状态,把second_thread转换为”running”状态,t2也获得g_num=0

  2. 然后second_thread对得到的值进行加1并赋给g_num,使得g_num=1

  3. 然后系统又把second_thread调度为”sleeping”,把first_thread转为”running”。线程t1又把它之前得到的0加1后赋值给g_num。

  4. 这样导致虽然first_thread和first_thread都对g_num加1,但结果仍然是g_num=1

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

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

线程同步的方式:

  1. 线程等待(join)

  2. 互斥锁

6.注意

if __name__ == '__main__':
    # 创建两个线程
    first_thread = threading.Thread(target=sum_num1)
    second_thread = threading.Thread(target=sum_num2)
    # 启动线程
    first_thread.start()
    #启动线程等待
    first_thread.join()
    # 启动线程
    second_thread.start()
# 创建锁
mutex = threading.Lock()

# 上锁
mutex.acquire()

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

# 释放锁
mutex.release()

7.使用互斥锁改写2个线程对同一个全局变量各加100万次的操作

import threading

# 定义全局变量
g_num = 0
# 创建全局互斥锁
lock = threading.Lock()

# 循环一次给全局变量加1
def sum_num1():
    # 上锁
    lock.acquire()
    for i in range(1000000):
        global g_num
        g_num += 1

    print("sum1:", g_num)
    # 释放锁
    lock.release()

# 循环一次给全局变量加1
def sum_num2():
    # 上锁
    lock.acquire()
    for i in range(1000000):
        global g_num
        g_num += 1
    print("sum2:", g_num)
    # 释放锁
    lock.release()

if __name__ == '__main__':
    # 创建两个线程
    first_thread = threading.Thread(target=sum_num1)
    second_thread = threading.Thread(target=sum_num2)
    # 启动线程
    first_thread.start()
    second_thread.start()

    # 提示:加上互斥锁,那个线程抢到这个锁我们决定不了,那线程抢到锁那个线程先执行,没有抢到的线程需要等待
    # 加上互斥锁多任务瞬间变成单任务,性能会下降,也就是说同一时刻只能有一个线程去执行

执行结果:

sum1: 1000000
sum2: 2000000

通过执行结果可以地址互斥锁能够保证多个线程访问共享数据不会出现数据错误问题.

小结:

8.死锁

**死锁是什么: **

死锁的危害:

总结:

9.进程与线程的对比

关系对比:

区别对比:

优缺点对比:

关于GIL锁:
这里不得不提的是python中全局GIL锁的问题,使得多线程并不能是真正的多线程,而是同一时间只能执行一个线程,其在计算场景及大部分计算情景下的效率提升显得鸡肋,甚至还不如单线程。
参考网址:http://cenalulu.github.io/python/gil-in-python/
引用一段解释:

转一篇关于Python GIL的文章。
归纳一下,CPU的大规模电路设计基本已经到了物理意义的尽头,所有厂商们都开始转向多核以进一步提高性能。Python为了能利用多核多线程的的优势,但又要保证线程之间数据完整性和状态同步,就采用了最简单的加锁的方式(所以说Python的GIL是设计之初一时偷懒造成的!)。Python库的开发者们接受了这个设定,即默认Python是thread-safe,所以开始大量依赖这个特性,无需在实现时考虑额外的内存锁和同步操作。但是GIL的设计有时会显得笨拙低效,但是此时由于内置库和第三方库已经对GIL形成了牢不可破的依赖,想改革GIL反而变得困难了(晕!)。所以目前的现状就是,Python的多线程在多核CPU上,只对于IO密集型计算产生正面效果;而当有至少有一个CPU密集型线程存在,那么多线程效率会由于GIL而大幅下降。虽然Python社区也在不断为此努力改进,但恐怕短时间内不会有改变,所以想规避GIL的,可以使用多进程的multiprocessing或concurrent.futures模块,或者换个Python的解析器。

作者:SeanCheney
链接:https://www.jianshu.com/p/9eb586b64bdb
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
完。

上一篇 下一篇

猜你喜欢

热点阅读