【多任务编程】线程

【多任务编程】线程



前言

本篇文章将介绍多任务编程中的线程,多线程的使用,线程执行带有参数的任务,线程注意点,互斥锁,死锁。


一、线程

1.线程的介绍

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

2.线程的作用

在这里插入图片描述


二、多线程的使用

#导入线程模块
import threading

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

# 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 dance():
    # 获取当前线程
    current_thread = threading.current_thread()
    print('sing:', 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()
    

总结:
1.导入线程模块
import threading
2.创建子线程并指定执行的任务
sub_thread = threading.Thread(target=任务名)
3.启动线程执行任务
sub_thread.start()


三、线程执行带有参数的任务

前面我们使用线程执行的任务是没有参数的,假如我们使用线程执行的任务带有参数,如何给函数传参呢?

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

  • args 表示以元组的方式给执行任务传参
  • kwargs 表示以字典方式给执行任务传参
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=('李四', 18))
    # # 启动子线程
    # sub_thread.start()

    # 以字典的方式传参, 要保证字典里面的key和函数的参数名保持一致
    sub_thread = threading.Thread(target=show_info, kwargs={'name': '王五', 'age': 19})
    sub_thread.start()



四、线程注意点

  • 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调度决定的

运行结果:
在这里插入图片描述

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

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()

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

运行结果:
在这里插入图片描述

  • 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,deamon=True)
    sub_thread = threading.Thread(target=task)
    # 把子线程设置成为主线程
    sub_thread.setDaemon(True)
    sub_thread.start()
    
    # 主线程延时执行1s
    time.sleep(1)
    print('over')

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

# 解决办法: 把子线程设置成为守护主线程即可

我们可以设置守护主线程
守护主线程:
守护主线程就是主线程退出子线程销毁不再执行
设置守护主线程有两种方式:
threading.Thread(target=show_info, daemon=True)
线程对象.setDaemon(True)

  • 3.线程之间共享全局变量
import threading
import time

# 定义全局变量
g_list = list()
print(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()

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

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

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

import threading

# 全局变量
g_num = 0


# 循环100万次执行的任务
def task1():
    global g_num
    for i in range(1000000):
        # 每循环一次给全局变量+1
        g_num = g_num + 1
    # 代码执行到此,说明数据计算完成
    print('task1:', g_num)


# 循环100万次执行的任务
def task2():
    global g_num
    for i in range(1000000):
        # 每循环一次给全局变量+1
        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()
    # first_thread.join()
    second_thread.start()

运行结果:
在这里插入图片描述

全局变量数据错误的解决办法:
线程同步: 保证同一时刻只能有一个线程去操作全局变量 同步: 就是协同步调,按预定的先后次序进行运行。
线程同步的方式:
1.线程等待(join)
2.互斥锁

小结:
线程执行执行是无序的
主线程默认会等待所有子线程执行结束再结束,设置守护主线程的目的是主线程退出子线程销毁。
线程之间共享全局变量,好处是可以对全局变量的数据进行共享。
线程之间共享全局变量可能会导致数据出现错误问题,可以使用线程同步方式来解决这个问题。线程等待(join)


五、互斥锁

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

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

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

互斥锁使用步骤:
#创建锁
mutex = threading.Lock()
#上锁
mutex.acquire()
…这里编写代码能保证同一时刻只能有一个线程去操作, 对共享数据进行锁定…
#释放锁
mutex.release()
注意点:
acquire和release方法之间的代码同一时刻只能有一个线程去操作
如果在调用acquire方法的时候 其他线程已经使用了这个互斥锁,那么此时acquire方法会堵塞,直到这个互斥锁释放后才能再次上锁。

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()

    # 互斥锁可以保证同一时刻只有一个线程去执行代码,能够保证全局变量的数据没有问题
    # 线程等待和互斥锁都是把多任务改成单任务去执行,保证了数据的准确性,但是执行性能会下降



六、死锁

死锁: 一直等待对方释放锁的情景就是死锁
死锁的结果:
会造成应用程序的停止响应,不能再处理其它任务了。

# 死锁: 一直等待对方释放锁的情景叫做死锁
import threading

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

# 需求: 多线程同时根据下标在列表中取值,要保证同一时刻只能用一个线程去取值

def get_value(index):
    # 上锁
    lock.acquire()
    my_list = [1, 4, 4]
    # 判断下标是否越界
    if index >= len(my_list):
        # 取值不成功,也需要释放互斥锁,不要影响后面的线程去取值
        # 锁需要在合适的地方进行释放,防止死锁
        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()

使用互斥锁的时候需要注意死锁的问题,要在合适的地方注意释放锁。
死锁一旦产生就会造成应用程序的停止响应,应用程序无法再继续往下执行了。

总结

到这里这篇文章的内容就结束了,谢谢大家的观看,如果有好的建议可以留言喔,谢谢大家啦!

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值