线程的用法

Hello,大家好。本期来和大家一起学习一下 线程的相关知识。

基础知识补充

首先,我们要了解一下并发和并行。

并发

并发是针对单核 CPU 提出的,指一个处理器同时处理多个任务。

相信大家看到这,有些疑惑?图中CPU在一个工作时间点,不是只在处理一个任务吗?为什么会同时处理多个任务?

就让我再举一个贴近大家生活的例子

假设我们的电脑配置为单核CPU,电脑正在运行四个程序:QQ、微信、浏览器、腾讯会议。

按照我们正常使用逻辑,会发现这四个程序是同时运行的。

那么其中的原理是怎样的呢?

从图中可以发现,CPU先处理QQ一小段时间,然后退出程序再处理微信一小段时间,. . . ,最后退出程序再处理微信一小段时间。这种方式也叫做轮训。

原来虽然 单核CPU 在同一时刻只能执行一个任务,但是通过将 CPU 的使用权在恰当的时机分配给不同的任务,使得多个任务在视觉上看起来是一起执行的。CPU 的执行速度极快,多任务切换的时间也极短,用户根本感受不到,所以并发执行看起来才跟真的一样。

并行

并行则是针对多核 CPU 提出的。和单核 CPU 不同,多核 CPU 真正实现了“同时执行,多个任务

多核 CPU 的每个核心都可以独立地执行一个任务,而且多个核心之间不会相互干扰。在不同核心上执行的多个任务,是真正地同时运行,这种状态就叫做并行

双核 CPU 执行两个任务时,每个核心各自执行一个任务,和单核 CPU 在两个任务之间不断切换相比,它的执行效率更高。

并发+并行

在图2中,执行任务的数量恰好等于 CPU 核心的数量,是一种理想状态。但是在实际场景中,处于运行状态的任务是非常多的,尤其是电脑和手机,开机就几十个任务,而 CPU 往往只有 4 核、8 核或者 16 核,远低于任务的数量,这个时候就会同时存在并发和并行两种情况:所有核心都要并行工作,并且每个核心还要并发工作。

例如一个双核 CPU 要执行四个任务,它的工作状态如下图所示:

每个核心并发执行两个任务,两个核心并行的话就能执行四个任务。当然也可以一个核心执行一个任务,另一个核心并发执行三个任务,这跟操作系统的分配方式,以及每个任务的工作状态有关系。

总结

并发针对单核 CPU 而言,它指的是 CPU 交替执行不同任务的能力;并行针对多核 CPU 而言,它指的是多个核心同时执行多个任务的能力。

单核 CPU 只能并发,无法并行;换句话说,并行只可能发生在多核 CPU 中。

在多核 CPU 中,并发和并行一般都会同时存在,它们都是提高 CPU 处理任务能力的重要手段。

线程

概念

线程是cpu的最小执行单位,是计算机cpu的一次调度执行。

线程的五种状态:

  • 新建状态(new)

  • 就绪状态(Runnable)

  • 运行状态(Running)

  • 阻塞状态(Blocked)

  • 死亡状态(Dead)

单线程

import time


def sing():
    for i in range(5):
        print('正在唱歌...')
        time.sleep(1)


def dance():
    for i in range(5):
        print('正在跳舞...')
        time.sleep(1)


def main():
    sing()
    dance()


if __name__ == '__main__':
    main()
'''
正在唱歌...
正在唱歌...
正在唱歌...
正在唱歌...
正在跳舞...
正在跳舞...
正在跳舞...
正在跳舞...
'''

多线程

python完成多任务,需要使用python的内置包:threading

1)创建线程对象:

t1 = threading.Thread(target=sing)

2)运行线程:

t1.start()

3)线程等待:

t1.join()

4)查看当前运行的线程:

print(threading.enumerate())

5)查看线程名称

print(threading.current_thread().name)

import time
import threading


def sing():
    for i in range(5):
        print('正在唱歌')
    time.sleep(1)


def dance():
    for i in range(5):
        print('正在跳舞')
        time.sleep(1)


def main():
    # 1.创建线程对象
    """
    在实例化线程对象时,需要指定当前创建的线程对象要运行什么任务?
    通过target参数指定一个任务(地址),args以元组的形式传参。
    """
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
    # 2.内部创建线程并运行
    t1.start()
    t2.start()


if __name__ == '__main__':
    main()
'''
正在唱歌...
正在跳舞...
正在唱歌...
正在跳舞...
正在唱歌...正在跳舞...
正在跳舞...
正在唱歌...
'''
交替执行,两个一起出来
特点:在线程执行的过程中,执行线程的顺序时随机的。在代码运行时,只用一个线程被执行。

多线程存在的问题

(问题一)线程间共享全局变量 --> 资源竞争问题

共享全局变量案例:

import threading

# 定义全局变量
g_num = 100


def test1():
    global g_num
    g_num += 1
    print('test1:%d' % g_num)


def test2():
    print('test2:%d' % g_num)


def main():
    t1 = threading.Thread(target=test1)
    t2 = threading.Thread(target=test2)

    t1.start()
    t2.start()
    print('main Thread:%d' % g_num)


if __name__ == '__main__':
    main()
"""
test1:101
test2:101main Thread:101
"""

资源竞争问题

当单个线程任务量较大时,就会出现资源竞争问题

import threading
import time

g_num = 0


def test1(num):
    global g_num
    for i in range(num):
        g_num += 1
    print('test1:%d' % g_num)


def test2(num):
    global g_num
    for i in range(num):
        g_num += 1
    print('test2:%d' % g_num)


def main():
    t1 = threading.Thread(target=test1, args=(1000000,))
    t2 = threading.Thread(target=test2, args=(1000000,))

    t1.start()
    t2.start()
    time.sleep(2)
    print('main Thread:%d' % g_num)


if __name__ == '__main__':
    main()
"""
test1:1414335
test2:1747168
main Thread:1747168
"""

原因分析

在程序中进行数据的运算会分为好几步:1.获取全局变量的值 2.对当前这个值 +1 3.将得出的结果赋值给全局变量

线程1在去执行计算时得出的结果还没有来得及赋值就进行了任务切换

问题解决:使用互斥锁解决资源竞争问题

1)声明一个互斥锁锁变量

mutex = threading.Lock()

2)上锁

mutex.acquire()

3)释放锁

mutex.release()

互斥锁案例:

import time
import threading

g_num = 0
# 声明一个互斥锁锁变量
mutex = threading.Lock()


def test1(num):
    global g_num

    for i in range(num):
        # 上锁
        mutex.acquire()
        g_num += 1
        # 释放锁
        mutex.release()

    print('test1: %d' % g_num)


def test2(num):
    global g_num

    for i in range(num):
        # 上锁
        mutex.acquire()
        g_num += 1
        # 释放锁
        mutex.release()
    print('test2: %d' % g_num)


def main():
    t1 = threading.Thread(target=test1, args=(1000000,))
    t2 = threading.Thread(target=test2, args=(1000000,))

    t1.start()
    t2.start()

    time.sleep(2)
    print('main Thread: %d' % g_num)


if __name__ == '__main__':
    main()

'''
test1: 1936038
test2: 2000000
main Thread: 2000000
'''

(问题二)使用互斥锁 --> 死锁问题

死锁案例:

import threading
import time

class MyThread1(threading.Thread):
    def run(self):
        # 对mutexA上锁
        mutexA.acquire()

        # mutexA上锁后,延时1秒,等待另外那个线程 把mutexB上锁
        print(self.name+'----do1---up----')
        time.sleep(1)

        # 此时会堵塞,因为这个mutexB已经被另外的线程抢先上锁了
        mutexB.acquire()
        print(self.name+'----do1---down----')
        mutexB.release()

        # 对mutexA解锁
        mutexA.release()


class MyThread2(threading.Thread):
    def run(self):
        # 对mutexB上锁
        mutexB.acquire()

        # mutexB上锁后,延时1秒,等待另外那个线程 把mutexA上锁
        print(self.name+'----do2---up----')
        time.sleep(1)

        # 此时会堵塞,因为这个mutexA已经被另外的线程抢先上锁了
        mutexA.acquire()
        print(self.name+'----do2---down----')
        mutexA.release()

        # 对mutexB解锁
        mutexB.release()

mutexA = threading.Lock()
mutexB = threading.Lock()

if __name__ == '__main__':
    t1 = MyThread1()
    t2 = MyThread2()
    t1.start()
    t2.start()

解决死锁的最好方法就是:只声明一把锁。

线程池

为什么要学线程池?

  • 因为手动开启线程比较麻烦

  • 如果线程数量过多会导致系统卡顿

所以我们可以创建一个指定数量的线程池来创建线程。

使用到的python第三方库为:

from concurrent.futures import ThreadPoolExecuto

1)创建一个包含2条线程的线程池

pool = ThreadPoolExecutor(max_workers=2)

2)向线程池提交一个task,10作为action()函数的参数

futrue = pool.submit(action, 10)

3)判断future代表的任务是否结束

print(futrue1.done())

4)查看futrue代表的任务返回的结果

print(futrue1.result())

5)关闭线程池

pool.shutdown()

线程池案例:

from concurrent.futures import ThreadPoolExecutor

my_sum = 0


# 任务函数
def action(max):
    global my_sum
    my_sum += max
    print(my_sum)


# 创建一个包含2条线程的线程池
with ThreadPoolExecutor(2) as f:
    for i in range(1, 101):
        # 向线程池提交一个任务,i作为action()函数的参数
        f.submit(action, max=i)

关于线程的更多用法,欢迎小伙伴后台留言哦。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值