Python基础之线程概念

Python基础之多线程概念

1、程序,进程,线程,多进程,多线程

首先,先来了解一下多线程中的几个概念:
在这里插入图片描述
在学校老师讲操作系统时,经常说的一句话就是:
进程是资源分配的最小单位,线程是CPU调度的最小单位。

  • 怎么理解呢?
    简单地来说,线程是进程中的一个执行任务(控制单元),负责进程中程序的执行。一个进程至少有一个线程,一个进程中的多个线程可以共享数据。所以线程又被成为轻量级进程。
  • 多进程:
    对于开发来说,多进程的概念更趋向于多个进程同时去完成一项工作。比如在Python开发中,Python在实现Python解释器(CPython)时引入GIL锁,使得一个进程任何时候仅有一个线程能执行,这样一来,多线程的效率可能还比不上单线程。因此使用多进程来避免这个限制。
  • 多线程:
    再来说说多线程,当你在使用一个文本程序时,该程序由三个进程组成:输入接收进程A,显示进程B,写入硬盘进程C。而他们共同需要的东西就是文本,因此三者就难免需要通信,而进程间频繁的通信势必会损失性能,因此,如果在一个进程中使用多线程来完成这个程序,一个进程内的多线程通信效率高,这样就能够保证性能了。
  • 总的来说,这个结构是这样的:
    程序 -> 进程 -> 线程

2、线程的生命周期

在这里插入图片描述

  • 1、New(新建):新创建的线程经过初始化,进入Runnable(就绪)状态。
  • 2、Runnbale(就绪):等待线程调度,调度后进入Running(运行)状态。
  • 3、Running(运行):线程正常运行,期间可能会因为某些情况进入Blocked(堵塞)状态。(同步锁:调用和sleep()和join()方法进入Sleeping状态;执行wait()方法进入Waiting状态,等待其他线程notify通知唤醒)。
  • 4、Blocked(阻塞):线程暂停运行,解除阻塞后进入Runnable(就绪)状态重新等待调度。
  • 5、Dead(死亡):线程完成了它的任务正常结束或因异常导致终止。

3、并行与并发

  • 并行:
    指两个或多个任务同时执行,即同时做不同事的能力。例如垃圾回收时,多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
  • 并发:
    指两个或多个任务在同一时间间隔内发生,即交替做不同事的能力,多线程是并发的一种形式。如垃圾回收时,用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续执行,而垃圾收集程序执行于另一个CPU上。

并行是同时处理多个任务,而并发则是处理多个任务,但不一定要同时,并行可以说是并发的子集。

4、同步与异步

  • 同步:线程执行某个请求,如果该请求需要一段时间才能返回信息,那么这个线程会一直等待,直到收到返回信息才能继续执行下去。
  • 异步:线程执行完某个请求,不需要一直等,直接执行后续操作,当有消息返回时系统会通知线程进程处理,这样可以提高执行的效率;异步在网络请求的应用非常常见。

5、如何解决同步安全问题(同步锁)

当多个线程访问临界资源(共享资源)的时候,有可能会出现线程安全问题;基本所有并发模式在解决线程安全问题时都采用“系列化访问资源”的方式,就是同一时刻,只有一个线程访问临界资源,也称“同步互斥访问”。通常的操作就是加锁(同步锁)。当有线程访问临界资源时需要获得这个锁,其他线程无法访问,只能等待(堵塞),等这个线程使用完释放锁,供其他线程继续访问。

6、与锁有关的特殊情况:死锁,饥饿与活锁

单单有了同步锁不意味着就一了百了了,当多个进程或线程的操作设计到了多个锁,就可能出现如下三种情况:

死锁

两个或以上的线程在执行中,因争夺资源而产生的一种互相等待的现象。若无外力作用,它们将无法推进下去。
死锁发生的条件:

  • 1、互斥条件:线程对资源的访问是排他性的,如果一个线程对占用了某资源,那么其他线程必须处于等待状态,直到资源被释放。
  • 2、请求和保持条件:线程T1至少已经爆出了一个资源R1占用,但又提出对另一个资源R2请求,而此时,资源R2被其他线程T2占用,于是线程T1也必须等待,但又对自己保持的资源R1不释放。
  • 3、不剥夺条件:线程已获得的资源,在未使用完之前,不能被其他线程剥夺,只能在使用完以后由自己释放。
  • 4、环路等待条件:在死锁发生时,必然存在一个“进程-资源环形链”,即进程P0等待P1占用的资源,P1等待P2占用的资源,Pn等待P0占用的资源。

饥饿与饿死

是指如果线程T1占用了资源R,线程T2又请求封锁R,于是T2等待。T3也请求资源R,当T1释放了R上的封锁后,系统首先批准了T3的请求,T2仍然等待。然后T4又请求封锁R,当T3释放了R上的封锁之后,系统又批准了T4的请求。。。。。。所以T2可能永远等待。

活锁

是指线程1可以使用资源,但它让其他线程先使用资源;线程2可以使用资源,但它也让其他线程先使用资源,于是两者一直谦让,都无法使用资源。
因此避免活锁的方法时采用先来先服务的策略。当多个事物请求封锁同一数据对象时,封锁子系统按请求封锁的先后顺序对事务进行排队。

7、守护线程

也叫后台线程,是一种为其他线程提供服务的线程。比如一个简单的例子:
当两个线程在协同做一件事,如果有一个线程死掉,事情就无法继续下去,此时就可以引入守护线程,轮询地去判断两个线程是否死掉,如果死掉就start开启线程,在Python中可以在线程初始化的时候调用setDaemon(True)把线程设置为守护线程,如果程序中只剩下守护线程,则自动退出。

  • 守护线程的作用:
    守护线程作用是为其他线程提供便利服务,守护线程最经典的应用就是GC(垃圾收集器)
  • 守护线程的特点:
    只要当前主线程中尚存任何一个非守护线程没有结束,守护线程就全部工作。
    只有当最后一个非守护线程结束时,守护线程随着主线程一同结束工作。

代码示例:
1、线程为非守护线程

import time
import threading


def fun():
    print("start fun")
    time.sleep(2)
    print("end fun")

def main():
    print("main thread")
    t1 = threading.Thread(target=fun,args=())
    t1.setDaemon(False)
    t1.start()
    time.sleep(1)
    print("main thread end")

if __name__ == '__main__':
    main()

执行结果如下:

main thread
start fun
main thread end
end fun

由此可见,程序在等待子线程结束,才会退出。

import time
import threading


def fun():
    print("start fun")
    time.sleep(2)
    print("end fun")

def main():
    print("main thread")
    t1 = threading.Thread(target=fun,args=())
    t1.setDaemon(True)
    t1.start()
    time.sleep(1)
    print("main thread end")

if __name__ == '__main__':
    main()

结果如下:

main thread
start fun
main thread end

说明:程序在主线程结束后,直接退出了。导致子线程没有运行完。

线程并发的经典问题:生产者与消费者

在线程中,生产者就是生产数据的线程,消费之就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者了。为了解决这个问题于是引入了生产者和消费者模式。

1、生产者消费者模型

生产者:比喻的是程序中负责产生数据的任务
消费者:比喻的是程序中负责处理数据的任务
生产者 --》共享的介质(队列)《–消费者

2、使用的优点

当我们的进程中存在明显的两类任务,一类负责产生数据,另一类负责处理数据
当时就应该考虑使用生产者消费者模型来提升程序的效率。

3、例子说明

下面我们实现这样一个过程

  • 维护一个整数列表interger_list,共有两个线程
  • Producer类对应一个线程,功能:随机产生一个整数,加入整数列表中
  • Consumer类对应一个线程,功能:从整数列表中pop掉一个整数
  • 通过time.sleep来表示两各线程运行速度,设置成Producer 产生的速度没有Consumer消耗的快
import time
import threading
import random


class Producer(threading.Thread):
    def __init__(self, condition, integer_list):
        threading.Thread.__init__(self)
        self.condition = condition
        self.integer_list = integer_list
    def run(self):
        while True:
            random_integer = random.randint(0, 100)
            with self.condition:
                self.integer_list.append(random_integer)
                print('integer list add integer {}'.format(random_integer))
                self.condition.notify()
            time.sleep(1.2 * random.random())



class Consumer(threading.Thread):
    def __init__(self, condition, integer_list):
        threading.Thread.__init__(self)
        self.condition = condition
        self.integer_list = integer_list
    def run(self):
        while True:
            with self.condition:
                if self.integer_list:
                    integer = self.integer_list.pop()
                    print('integer list lose integer {}'.format(integer))
                    time.sleep(random.random())
                else:
                    print('there is no integer in the list')
                    self.condition.wait()
def main():
    integer_list = []
    condition = threading.Condition()
    th1 = Producer(condition, integer_list)
    th2 = Consumer(condition, integer_list)
    th1.start()
    th2.start()
if __name__ == '__main__':
    main()

在生产出整数时,notify通知wait的线程可依据虚了
消费者查询到列表为空时调用wait等待通知(notify)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值