Python学习20--线程

本文详细介绍了Python中的线程概念,包括程序、进程和线程的关系,多线程的执行情况,以及多线程在单核和多核CPU下的表现。讨论了多线程的优缺点,并通过实例展示了线程的创建和生命周期。同时,讲解了线程同步的重要性,以解决线程安全问题,如使用线程锁避免资源冲突。最后,文章通过实例演示了死锁的概念,说明了并发编程中可能遇到的挑战。
摘要由CSDN通过智能技术生成

一、基本概念

程序:程序可以理解成是一系列的指令集,程序是静态
进程:当程序运行时,会创建一个进程
线程:进程基本执行单元,一个进程至少有一个线程

进程和线程之间的关系:
一个线程只属于一个进程,一个进程包含多个线程

进程资源、线程的对比:
进程具有独立的空间和系统资源
线程没有独立的空间和系统资源,同一个进程下的多个线程共享该进程下的资源

问题:
多线程中对于共享资源修改的问题—多线程同步问题,线程不安全

二、多线程

多线程在单核CPU下的执行状况:从宏观上可以认为是并行,从微观上看是串行
多线程在多核CPU下的执行状况:真正的并行

运用场合:计算型密集不适合单核CPU多线程,IO密集型适合单核CPU的多线程

多线程的缺点:
1、线程本身也是程序,也是需要占用内存,线程越多,占用的资源就越多
2、多线程之间的调用需要协调管理,需要cpu进行线程的跟踪
3、多线程之间对共享资源的访问有影响,必须解决线程共享资源问题,否则会导致数据的不一致

三、线程的创建

线程的创建有三种方式:
1、使用threading模块创建,指定target和args参数(必须以元组的形式传入),创建线程
2、使用Thread类,重写run方法
3、使用线程池

1、使用threading模块创建

import threading
import time
def mission(end):
    for i in range(end):
        print(i)
        time.sleep(0.5)

t1 = threading.Thread(target=mission,args=(2,))
t2 = threading.Thread(target=mission,args=(3,))
t1.start()
t2.start()

输出为:

0
0
1
1
2

target是传入函数的名字,args传入参数,必须要以元组的方式传入
start方法是激活线程,不是执行线程,把当前线程任务加入到CPU的任务执行列表中,等待cpu来分配时间片执行

2、使用Thread类,重写RUN方法

代码实现如下:

import threading
class MyThread(threading.Thread):
    def __init__(self,end):
        self.end = end
        super().__init__()
    def run(self):
        for i in range(self.end):
            print(i)

t1 = MyThread(2)
t2 = MyThread(3)
t1.start()
t2.start()

输出:

0
1
0
1
2

当需要创建很多个执行相同方法的线程时,建议使用继承Thread类,重写run方法。
run方法才是真正执行函数任务的方法。
run方法和start方法的区别:
start方法:激活线程,不代表真正的执行,使得线程处于就绪状态,加入cpu执行任务列表
run:真正执行任务的函数

四、线程的生命周期

1、新建:相当于人的出生,创建线程对象,没有执行能力
2、就绪:相当于等待就业,调用start方法,不是马上执行,而是把权利交给cpu
3、运行:相当于入职工作,执行线程的任务,获得cpu的时间片
4、阻塞:相当于生病,处于等待的过程中(调用sleep),cpu不会将时间分配给阻塞状态的进程
5、死亡:相当于死亡,run方法执行完毕,run方法中抛出没有捕获的异常

五、线程的相关操作

1、threading.active_count():计算处于活跃状态的线程数量
活跃状态指在调用start之后(就绪),处于死亡之前,包含就绪、运行、阻塞
如下所示:

import threading
class Mythread(threading.Thread):
    def __init__(self,end):
        self.end = end
        super().__init__()

    def run(self):
        for i in range(self.end):
            print(i)

t1 = Mythread(2)
t1.start()
print("活跃线程的数量是:{}".format(threading.active_count()))

2、threading.enumerate(),返回一个list,包含正在运行的线程
如下所示:

import threading
class Mythread(threading.Thread):
    def __init__(self,end):
        self.end = end
        super().__init__()

    def run(self):
        for i in range(self.end):
            print(i)

t1 = Mythread(2)
t1.start()
li = threading.enumerate()
print(li)
for i in li:
    print(i)

输出:

0
1
[<_MainThread(MainThread, started 96)>]
<_MainThread(MainThread, started 96)>

3、threading.get_ident():获得线程的标志,不同的线程标志不同,这个是唯一标志
使用如下:

import threading
def mission(end):
    print(threading.get_ident(),end="")

for i in range(5):
    t1 = threading.Thread(target=mission,args=(10,))
    t1.start()
    print("是第{}次执行的线程标志".format(i))

输出:

17772是第0次执行的线程标志
1768是第1次执行的线程标志
16140是第2次执行的线程标志
12644是第3次执行的线程标志
14152是第4次执行的线程标志

4、threading.main_thread():返回执行解释器的线程,主线程
使用如下:

import threading
print(threading.main_thread())

输出:

<_MainThread(MainThread, started 9780)>

5、threading.current_thread()获得当前执行的线程
使用如下:

import threading
print(threading.current_thread())

输出:

<_MainThread(MainThread, started 8248)>

6、start():启动线程,使线程处于就绪状态
7、run():运行线程内容

8、join(参数):抢占时间片
A线程中:B.join(参数),在A线程中插入B线程,B线程必须执行完毕,才能继续执行A线程
join(参数):如果不写参数,会抢占时间片直到抢占线程完毕。如果写参数,参数代表等待的时间

import threading
import time
def mission():
    print("进行修路中")
    time.sleep(1)
    print("修马路完毕")
t = threading.Thread(target=mission)
t.start()
print("想要过马路")

输出:

进行修路中
想要过马路
修马路完毕

程序原意是希望修完马路后再执行过马路的动作
当加入join后,执行情况如下:

import threading
import time
def mission():
    print("进行修路中")
    time.sleep(1)
    print("修马路完毕")
t = threading.Thread(target=mission)
t.start()
t.join()
print("想要过马路")

输出:

进行修路中
修马路完毕
想要过马路

如果在join中加入参数,如下所示:

import threading
import time
def mission():
    print("进行修路中")
    time.sleep(1)
    print("修马路完毕")
t = threading.Thread(target=mission)
t.start()
t.join(0.2)
print(t.is_alive())  # 判断线程是否存活,如果执行完毕,那么是死亡False
print("想要过马路")

执行状况为:

进行修路中
True
想要过马路
修马路完毕

join中的参数表示抢占的时间

9、name:给线程设置名字,私有属性
使用方法如下:

import threading
import time
def mission():
    print("进行修路中")
    t.name = "修马路的线程"
t = threading.Thread(target=mission)
t.start()
print(t.name)

输出:

进行修路中
修马路的线程

10、ident 线程标志 属性,get_ident()方法一样,线程的标志符
11、is_alive:判断线程是否存活
True:线程存活 False:线程死亡

12、daemon 设置是否是守护线程(后台线程)
默认的线程都是非守护线程(前台线程)
拿唱歌人和乐队做例子:
(1)如果将乐队设置为非守护线程:
唱歌的人唱完,乐队未演奏完,则乐队会接着演奏
唱歌的人未唱完,乐队演奏完,乐队演奏完就下台
两个线程是相互独立的线程

(2)如果将乐队设置为守护线程:
唱歌的人唱完,乐队未演奏完,则乐队停止演奏
唱歌的人未唱完,乐队演奏完,乐队继续留在台上直到唱歌的人演唱完成

即乐队的完成度以唱歌的人为主

设置守护线程,默认是False,代表非守护线程
格式为:t.setDaemon(Ture)
注意:设置守护线程必须在start之前
使用方式如下:

import threading
import time
def music():
    print("乐队线程正在开始")
    time.sleep(1)
    print("乐队还在伴奏")
    print("乐队伴奏结束")

if __name__ =="__main__":
    print("开始唱歌")
    t = threading.Thread(target=music)
    t.setDaemon(True)
    t.start()
    print("唱歌结束")

输出结果:

开始唱歌
乐队线程正在开始
唱歌结束

把music设置为守护线程后,如果主线程执行完而守护线程未执行完,那么守护线程也会停止执行

六、线程的同步

在同一个进程下面,共享很多线程,线程之间对于资源上是共享,共享会引发线程的不安全问题。
如抢票问题:对成员变量的操作进行共享。

import threading
import time
ticket = 10
def buy_ticket():
    global ticket
    while ticket>0:
        t = threading.current_thread()
        time.sleep(0.1)
        print("{}抢到了第{}张票".format(t.name,ticket))
        ticket-=1

t1 = threading.Thread(target=buy_ticket)
t1.name = "张三"

t2 = threading.Thread(target=buy_ticket)
t2.name = "李四"

t3 = threading.Thread(target=buy_ticket)
t3.name = "王五"
t1.start()
t2.start()
t3.start()

输出为:

张三抢到了第10张票
李四抢到了第10张票
王五抢到了第8张票
李四抢到了第7张票
张三抢到了第7张票
王五抢到了第5张票
张三抢到了第4张票
李四抢到了第3张票
王五抢到了第2张票
李四抢到了第1张票
王五抢到了第0张票
张三抢到了第-1张票

有抢到同一张票的,没有票的时候还在执行抢票动作
解决方法:使用线程锁,在同一个时间点内,一个共享资源只能被一个线程访问
lock.acquire():加锁
lock.realease():解锁
第一次解决:

import threading
import time
ticket = 10
lock = threading.Lock()
def buy_ticket():
    global ticket
    while ticket>0:
        lock.acquire()
        t = threading.current_thread()
        time.sleep(0.1)
        print("{}抢到了第{}张票".format(t.name,ticket))
        ticket-=1
        lock.release()

t1 = threading.Thread(target=buy_ticket)
t1.name = "张三"

t2 = threading.Thread(target=buy_ticket)
t2.name = "李四"

t3 = threading.Thread(target=buy_ticket)
t3.name = "王五"
t1.start()
t2.start()
t3.start()

输出:

张三抢到了第10张票
张三抢到了第9张票
张三抢到了第8张票
张三抢到了第7张票
张三抢到了第6张票
张三抢到了第5张票
张三抢到了第4张票
张三抢到了第3张票
张三抢到了第2张票
张三抢到了第1张票
王五抢到了第0张票
李四抢到了第-1张票

同票的问题解决了,但是出现了负数票

第二次解决:
把锁放在while之前

import threading
import time
ticket = 10
lock = threading.Lock()
def buy_ticket():
    global ticket
    lock.acquire()
    while ticket>0:

        t = threading.current_thread()
        time.sleep(0.2)
        print("{}抢到了第{}张票".format(t.name,ticket))
        ticket-=1
    lock.release()

t1 = threading.Thread(target=buy_ticket)
t1.name = "张三"

t2 = threading.Thread(target=buy_ticket)
t2.name = "李四"

t3 = threading.Thread(target=buy_ticket)
t3.name = "王五"
t1.start()
t2.start()
t3.start()

输出结果为:

张三抢到了第10张票
张三抢到了第9张票
张三抢到了第8张票
张三抢到了第7张票
张三抢到了第6张票
张三抢到了第5张票
张三抢到了第4张票
张三抢到了第3张票
张三抢到了第2张票
张三抢到了第1张票

只有张三一人抢到票,因为while循环会一直执行,直到票被抢完,才会执行结束释放出锁

第三次解决

import threading
import time
ticket = 10
lock = threading.Lock()
def buy_ticket():
    global ticket
    while True:
        try:
            lock.acquire()
            if ticket>0:
                t = threading.current_thread()
                time.sleep(0.2)
                print("{}抢到了第{}张票".format(t.name,ticket))
                ticket-=1
            else:
                break
        finally:
            lock.release()

t1 = threading.Thread(target=buy_ticket)
t1.name = "张三"

t2 = threading.Thread(target=buy_ticket)
t2.name = "李四"

t3 = threading.Thread(target=buy_ticket)
t3.name = "王五"
t1.start()
t2.start()
t3.start()

输出:

张三抢到了第10张票
张三抢到了第9张票
张三抢到了第8张票
张三抢到了第7张票
张三抢到了第6张票
张三抢到了第5张票
张三抢到了第4张票
张三抢到了第3张票
张三抢到了第2张票
张三抢到了第1张票

七、死锁

定义:当两个或者多个线程同时拥有自己的资源,而此时,互相等待获得对方的资源
什么时候会出现死锁:编程的时候,处理并发操作,使用锁解决共享资源问题的时候,会造成死锁
如下所示:

import threading
import time
lock1 = threading.Lock()
lock2 = threading.Lock()

def mission(l1,l2):
    l1.acquire()
    t = threading.current_thread()
    print("{}已经获得了一把锁,希望获得另一把锁".format(t.name))
    time.sleep(0.5)

    l2.acquire()
    l2.realease()
    l1.realease()

t1 = threading.Thread(target=mission,args=(lock1,lock2))
t2 = threading.Thread(target=mission,args=(lock2,lock1))

t1.start()
t2.start()

输出:

Thread-1已经获得了一把锁,希望获得另一把锁
Thread-2已经获得了一把锁,希望获得另一把锁

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值