前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >多任务编程 - 2

多任务编程 - 2

作者头像
用户9399690
发布2022-01-20 15:33:53
3510
发布2022-01-20 15:33:53
举报
文章被收录于专栏:码猴小明

有些事情不是难以做到才失去信心,而是因为失去信心才难以做到。 ——肖乾旭

线程

1、线程的介绍

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

2、线程的概念

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

3、线程的作用

多线程可以完成多任务

多线程效果图:

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

4、小结

  • 线程是Python程序中实现多任务的另外一种方式,线程的执行需要cpu进行调度来完成。

多线程的使用

1、导入线程模块

代码语言:javascript
复制
# 导入线程模块
import threading

2、线程类Thread参数说明

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

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

3、启动线程

启动线程使用start方法

4、多线程完成多任务的代码

代码演示:

代码语言:javascript
复制
# -*- codeing = utf-8 -*-
# @Time : 2021/12/7 8:29 下午
# @Author : 李明辉
# @File : ithui_多线程的使用.py
# @Software : PyCharm
# 1、导入线程模块
import threading
import time
def sing():
    # 获取当前线程
    current_thread = threading.current_thread()
    print("sing:", current_thread)
    for i in range(3):
        print('唱歌中...')
        time.sleep(0.2)
def dancee():
    # 获取当前线程
    current_thread = threading.current_thread()
    print("dance:", 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=dancee)
    # 3、启动子线程执行对应的任务
    sing_thread.start()
    dance_thread.start()

运行结果:

线程执行带有参数的任务

1、线程执行带有参数的任务的介绍

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

  • args表示以元组的方式给执行任务传参
  • kwargs表示以字典的方式给执行任务传参

2、args参数的使用

代码演示:

代码语言:javascript
复制
# -*- codeing = utf-8 -*-
# @Time : 2021/12/7 8:44 下午
# @Author : 李明辉
# @File : ithui_线程执行带有参数的任务.py
# @Software : PyCharm
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()

结果如下:

3、kwargs参数的使用:

代码演示:

代码语言:javascript
复制
# -*- codeing = utf-8 -*-
# @Time : 2021/12/7 8:44 下午
# @Author : 李明辉
# @File : ithui_线程执行带有参数的任务.py
# @Software : PyCharm
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()
    sub_thread = threading.Thread(target=show_info, kwargs={"name":"王五","age":20})
    sub_thread.start()

结果如下:

线程的注意点

1、线程的注意点介绍

  1. 线程之间执行是无序的
  2. 主线程会等待所有的子线程执行结束再结束
  3. 线程之间共享全局变量
  4. 线程之间共享全局数据出现错误问题

2、线程之间执行是无序的

代码演示:

代码语言:javascript
复制
# -*- codeing = utf-8 -*-
# @Time : 2021/12/7 8:54 下午
# @Author : 李明辉
# @File : ithui_线程之间执行是无序的.py
# @Software : PyCharm
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()

结果如下:

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

代码演示:

代码语言:javascript
复制
# -*- codeing = utf-8 -*-
# @Time : 2021/12/7 9:02 下午
# @Author : 李明辉
# @File : ithui_主线程会等待所有的子线程执行结束再结束.py
# @Software : PyCharm
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.start()
    # 主线程延时执行1秒
    time.sleep(1)
    
    print("over")
    
    # 结论:主线程会等待子线程执行结束再结束

结果如下:

3、线程之间共享全局变量

代码演示:

代码语言:javascript
复制
# -*- codeing = utf-8 -*-
# @Time : 2021/12/7 9:10 下午
# @Author : 李明辉
# @File : ithui_线程之间共享全局变量.py
# @Software : PyCharm
import threading
g_list = []
def add_data():
    for i in range(3):
        g_list.append(i)
        print("add:", i)
    print("添加数据完成", g_list)
def read_data():
    print(g_list)
if __name__ == '__main__':
    add_thread = threading.Thread(target=add_data)
    read_thread = threading.Thread(target=read_data)
    add_thread.start()
    read_thread.start()

结果如下:

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

代码演示:

代码语言:javascript
复制
# -*- codeing = utf-8 -*-
# @Time : 2021/12/7 9:18 下午
# @Author : 李明辉
# @File : ithui_线程之间共享全局数据出现错误问题.py
# @Software : PyCharm
import threading
g_num = 0
def task1():
    for i in range(1000000):
        global g_num # 表示要声明修改全局变量的内存地址
        g_num += 1
    print("task1", g_num)
def task2():
    for i in range(1000000):
        global g_num  # 表示要声明修改全局变量的内存地址
        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()
    second_thread.start()

结果如下:

错误分析:

两个线程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和second_thread都对g_num加1,但结果仍然是g_num=1

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

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

线程同步的方式:

  1. 线程等待(join)
  2. 互斥锁

互斥锁

1、互斥锁的概念

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

注意:

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

2、互斥锁的使用

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

互斥锁使用步骤:

代码语言:javascript
复制
# 创建锁
mutex = threading.lock()
代码语言:javascript
复制
# 上锁
mutex.acquire()

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

代码语言:javascript
复制
# 释放锁
mutex.release()

注意点:

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

3、使用互斥锁完成两个线程同时对一个全局变量各加100万次的操作

代码演示:

代码语言:javascript
复制
# -*- codeing = utf-8 -*-
# @Time : 2021/12/7 9:53 下午
# @Author : 李明辉
# @File : ithui_互斥锁.py
# @Software : PyCharm
# -*- codeing = utf-8 -*-
# @Time : 2021/12/7 9:18 下午
# @Author : 李明辉
# @File : ithui_线程之间共享全局数据出现错误问题.py
# @Software : PyCharm
import threading
g_num = 0
# 创建互斥锁
lock = threading.Lock()
def task1():
    # 上锁
    lock.acquire()
    for i in range(1000000):
        global g_num # 表示要声明修改全局变量的内存地址
        g_num += 1
    print("task1", g_num)
    # 释放
    lock.release()
def task2():
    # 上锁
    lock.acquire()
    for i in range(1000000):
        global g_num  # 表示要声明修改全局变量的内存地址
        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()

结果如下:

说明:

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

4、小结

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

死锁

1、死锁的概念

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

说明:现实生活中,男女双方一直等待 对方先道歉的这种行为就好比是死锁。

进程和线程的对比

1、进程和线程的对比的三个方向

  1. 关系对比
  2. 区别对比
  3. 优缺点对比

2、关系对比

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

3、区别对比

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

4、优缺点对比

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

5、小结

  • 进程和线程都是完成多任务的一种方式
  • 多进程要比多线程消耗的资源多,但是多进程开发比单进程开发稳定性要强,某个进程挂掉不会影响其他的进程
  • 多进程可以使用CPU的多核运行,多线程可以共享全局变量

如果前面还不太了解的朋友可以看看《多任务编程 - 1》哦

多任务编程 - 1

END

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-12-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码猴小明 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档