线程知识及编程

线程定义

在Python中,想要实现多任务还可以使用多线程来完成。

为什么使用多线程?

进程是分配资源的最小单位 , 一旦创建一个进程就会分配一定的资源 , 就像跟两个人聊QQ就需要打开两个QQ软件一样是比较浪费资源的 .

线程是程序执行的最小单位 , 实际上进程只负责分配资源 , 而利用这些资源执行程序的是线程 , 也就说进程是线程的容器 , 一个进程中最少有一个线程来负责执行程序 . 同时线程自己不拥有系统资源,只需要一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源 . 这就像通过一个QQ软件(一个进程)打开两个窗口(两个线程)跟两个人聊天一样 , 实现多任务的同时也节省了资源 .

在这里插入图片描述

多线程完成多任务

线程的创建步骤

  1. 导入线程模块

    import threading
    
  2. 通过线程类创建线程对象

    线程对象 = threading.Thread(target=任务名)
    
  3. 启动线程执行任务

    线程对象.start()
    

通过线程类创建线程对象

在这里插入图片描述

代码演示

import time
import threading


# 编写代码
def coding():
    for i in range(3):
        print("coding...")
        time.sleep(0.2)


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


if __name__ == '__main__':
    # coding()
    # music()
    # 创建子线程
    coding_thread = threading.Thread(target=coding)
    music_thread = threading.Thread(target=music)

    # 启动子线程执行任务
    coding_thread.start()
    music_thread.start()

线程执行带有参数的任务

参数名说明
args以元组的方式给执行任务传参
kwargs以字典方式给执行任务传参
import time
import threading


# 编写代码
def coding(num):
    for i in range(num):
        print("coding...")
        time.sleep(0.2)


# 听音乐
def music(count):
    for i in range(count):
        print("music...")
        time.sleep(0.2)


if __name__ == '__main__':
    # coding()
    # music()
    # 创建子线程
    coding_thread = threading.Thread(target=coding, args=(3,))
    music_thread = threading.Thread(target=music, kwargs={"count" : 1})

    # 启动子线程执行任务
    coding_thread.start()
    music_thread.start()

线程执行带有参数的任务传参有两种方式:

1.元组方式传参 :元组方式传参一定要和参数的顺序保持一致

2.字典方式传参:字典方式传参字典中的key一定要和参数名保持一致

主线程和子线程的结束顺序

设置守护主线程

设置守护主线程的目的是主线程退出子线程销毁,不让主线程再等待子线程去执行。

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

threading.Thread(target=work, daemon=True)
线程对象.setDaemon(True)

threading.enumerate() 获取当前所有活跃的线程对象列表。使用 len() 对列表求长度可以看到当前活跃的线程的个数

主线程可以不断判断活跃线程个数,如果个数小于等于1,说明其它线程已经执行完成

import time
import threading


# 工作函数
def work():
    for i in range(10):
        print("work...")
        time.sleep(0.2)


if __name__ == '__main__':
    # 创建子线程
    # 方式1 参数方式设置守护主线程
    # work_thread = threading.Thread(target=work, daemon=True)
    work_thread = threading.Thread(target=work)
    # 启动线程
    # 方式2
    work_thread.setDaemon(True)
    work_thread.start()

    # 延时一秒
    time.sleep(1)
    print("主线程执行完毕")

线程间的执行顺序

在这里插入图片描述

线程之间执行是无序的

由CPU调度决定某个线程先执行的

import threading
import time


# 获取线程信息函数
def get_info():
    time.sleep(0.5)
    # 获取线程信息
    current_thread = threading.current_thread()
    print(current_thread)


if __name__ == '__main__':
    # 创建子线程
    for i in range(10):
        sub_thread = threading.Thread(target=get_info)
        sub_thread.start()
import threading


# 全局变量
g_num = 0


# 对g_num进行加操作
def sum_num1():
    for i in range(1000000):
        global g_num
        g_num += 1

    print("g_num1:", g_num)


# 对g_num进行加操作
def sum_num2():
    for i in range(1000000):
        global g_num
        g_num += 1

    print("g_num2:", g_num)


if __name__ == '__main__':
    # 创建子线程
    sum1_thread = threading.Thread(target=sum_num1)
    sum2_thread = threading.Thread(target=sum_num2)

    # 启动线程
    sum1_thread.start()
    sum2_thread.start()

互斥锁的使用

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

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

使用

  1. 互斥锁的创建

    mutex = threading.Lock()
    
  2. 上锁

    mutex.acquire()
    
  3. 释放锁

mutex.release()

代码演示

import threading


# 全局变量
g_num = 0


# 对g_num进行加操作
def sum_num1():
    # 上锁
    mutex.acquire()

    for i in range(1000000):
        global g_num
        g_num += 1

    # 解锁
    mutex.release()

    print("g_num1:", g_num)


# 对g_num进行加操作
def sum_num2():
    # 上锁
    mutex.acquire()

    for i in range(1000000):
        global g_num
        g_num += 1

    # 解锁
    mutex.release()

    print("g_num2:", g_num)


if __name__ == '__main__':
    # 创建锁
    mutex = threading.Lock()
    # 创建子线程
    sum1_thread = threading.Thread(target=sum_num1)
    sum2_thread = threading.Thread(target=sum_num2)

    # 启动线程
    sum1_thread.start()
    sum2_thread.start()

死锁

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

死锁的结果:

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

产生死锁的原因:没有及时或者在正确的位置释放锁

注意点:

1.使用互斥锁的时候需要注意死锁的问题,要在合适的地方注意释放锁。

2.死锁一旦产生就会造成应用程序的停止响应,应用程序无法再继续往下执行了。

import threading


# 全局变量
g_num = 0


# 对g_num进行加操作
def sum_num1():
    # 上锁
    print("sun_num1...")
    mutex.acquire()

    for i in range(1000000):
        global g_num
        g_num += 1

    print("g_num1:", g_num)


# 对g_num进行加操作
def sum_num2():
    # 上锁
    print("sun_num2...")
    mutex.acquire()

    for i in range(1000000):
        global g_num
        g_num += 1

    print("g_num2:", g_num)


if __name__ == '__main__':
    # 创建锁
    mutex = threading.Lock()
    # 创建子线程
    sum1_thread = threading.Thread(target=sum_num1)
    sum2_thread = threading.Thread(target=sum_num2)

    # 启动线程
    sum1_thread.start()
    sum2_thread.start()

进程和线程对比

关系对比

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

区别对比

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

  2. 线程之间共享全局变量,但是要注意资源竞争的问题,解决办法: 互斥锁或者线程同步

  3. 创建进程的资源开销要比创建线程的资源开销要大

  4. 进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位

  5. 线程不能够独立执行,必须依存在进程中

优缺点对比

1.进程—优点:可以用多核。缺点:资源开销大

2.线程—优点:资源开销小。缺点:不能使用多核

sum2_thread = threading.Thread(target=sum_num2)

# 启动线程
sum1_thread.start()
sum2_thread.start()

进程和线程对比

关系对比

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

区别对比

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

  2. 线程之间共享全局变量,但是要注意资源竞争的问题,解决办法: 互斥锁或者线程同步

  3. 创建进程的资源开销要比创建线程的资源开销要大

  4. 进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位

  5. 线程不能够独立执行,必须依存在进程中

优缺点对比

进程—优点:可以用多核。缺点:资源开销大

线程—优点:资源开销小。缺点:不能使用多核

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值