Python 学习笔记-第17讲:thread 线程

本文介绍了进程与线程的基本概念,包括进程的特点、线程与进程的区别与联系,以及多线程的优点和Python中实现多线程的方法。此外,还详细探讨了多线程同步的重要性,通过锁和条件变量确保线程安全。

一、进程与线程
1. 进程是一个程序的执行实例,是操作系统可分配、管理的资源集合。进程拥有唯一的 ID。
特点:
1) 进程是系统运行程序的基本单位。
2) 每一个进程都有自己独立的一块内存空间和一组系统资源。
3) 每一个进程的内部数据和状态都是完全独立的。
2. 进程内用于执行不同子任务的一组指令称为线程,可并发执行。线程是 CPU 调度的最小单位,进程不能够直接与 CPU 进行交互,必须通过线程才可以。
一个进程至少有一个线程,进程会启动一个主线程,主线程的 ID 与 进程的 ID 相同。
主线程可以创建子线程,子线程还可以创建线程。
3. 线程与进程的区别和联系:
1) 同一个进程中的线程共享内存空间,不同进程的内存空间是相互独立的。
2) 同一个进程中的多个线程共享一份数据,而子进程之间的数据时相互独立的
3) 同一个进程中的多个线程之间可以直接进行通信,两个进程之间进行通信必须使用中间代理。
4) 创建进程的开销较大,需要对父进程进行完全拷贝。而创建线程的开销较小。
5) 同一个进程中的线程可以创建其他线程,创建完成后两个线程之间关系平等。进程可以创建进程,但是会有主进程和子进程之分。
6) 同一个进程中的线程正常退出不会影响其他线程,线程崩溃会导致进程崩溃。父进程被杀不会影响其子进程,除非子进程做特殊处理。
7) 同一个进程中的线程操作数据,有可能会影响其他线程的数据。在父进程中操作数据,一定不会影响其子进程中的数据。
二、多线程
1. 多线程类似于同时执行多个不同程序,多线程运行有如下优点:

使用线程可以把占据长时间的程序中的任务放到后台去处理。
用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度
程序的运行速度可能加快
在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。

2. python中使用线程有两种方式,第一种是用_thread模块的start_new_thread函数,另一种是用threading模块的Thread类来包装线程对象。
threading模块提供的类:
  Thread, Lock, Rlock, Condition, [Bounded]Semaphore, Event, Timer, local。

threading 模块提供的常用方法:
  threading.currentThread(): 返回当前的线程变量。
  threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

threading 模块提供的常量:

  threading.TIMEOUT_MAX 设置threading全局超时时间。

使用Thread类有两种方式,一种是直接创建Thread类的实例,另一种方式是自定义类来继承Thread类。使用Thread类的实例,在它的初始化函数__init__中将可调用的对象作为参数传入,Thread的初始化函数__init__原型:

__init__(self, group=None, target=None, name=None, args=(), kwargs=None, verbose=None);
import threading
from time import sleep

def test():
    for i in range(10):
        print(i,'running....')
        sleep(0.5)

t=threading.Thread(target=test)
t.start()

自定义类来继承Thread类,然后重写run方法:

import threading
from time import sleep


class MyThread(threading.Thread):
    # def __init__(self,name):
    #     super().__init__()
    #     # super(MyThread, self).__init__()
    #     self.name=name


    def run(self):
        for i in range(100):
            print(self.name,'run...')
            # sleep(0.1)

mythread=MyThread()
mythread2=MyThread()
mythread.start()
mythread2.start()
mythread2.join()
start()后线程没有立即运行而是变成就绪状态,当得到cpu时间片后变成运行状态,当run方法结束或者有未处理的异常时,线程将结束,变成死亡状态等待被回收,在运行状态如果遇到资源没有准备好就会变成阻塞状态,当得到资源后再次变成就绪状态等待 CPU 调度。

优秀博文:


三、多线程同步
当多个线程同时进行任务时,为了保证不会有多个线程同时对同一个数据进行操作造成不可预料的后果,所以有了锁的概念,我们通过锁来使多线程任务更加安全。
lock = threading.Lock()
cond = threading.Condition(lock=lock)

锁有锁定和未锁定两种状态,当一个线程要访问共享数据时,必须要先获得锁定,如果已经有别的线程获得锁定,那么就进入暂停状态,等别的线程把锁释放后,再进行操作。
Condition:更精确的控制锁,提供了四个方法,上锁(acquire()),等待(wait()),解锁(release()),唤醒(notify(),notify_all())
Condition不仅提供了锁的acquire和release方法,还提供了wait,notify和notifyAll方法。当线程通过acquire获得锁后因为某些资源的却是需要等待就调用wait方法进入等待状态,同时释放锁持有的锁让别的线程能够获得并执行,当资源准备充足的时候就可以调用notify通知其他线程,处于等待状态的线程被唤醒重新去acquire锁,或者wait的线程超时,因为wait有个参数timeout。Condition内部维护着一个锁对象,可以通过参数指定为Lock或者RLock,默认为RLock,还维护着一个waiting池,进入等待状态的线程就会被这个waiting池锁记录,当有线程调用了notify后,Condition就会从这个waiting池中找一个线程唤醒,通知它去acquire锁进入就绪状态等待被cpu的调度,而notifyAll就会唤醒waiting池中所有的线程通知它们去acquire锁。

Condition(条件变量)通常与一个锁关联。需要在多个Contidion中共享一个锁时,可以传递一个Lock/RLock实例给构造方法,否则它将自己生成一个RLock实例。
  可以认为,除了Lock带有的锁定池外,Condition还包含一个等待池,池中的线程处于等待阻塞状态,直到另一个线程调用notify()/notifyAll()通知;得到通知后线程进入锁定池等待锁定。
构造方法: 
Condition([lock/rlock])
实例方法: 
  acquire([timeout])/release(): 调用关联的锁的相应方法。 
  wait([timeout]): 调用这个方法将使线程进入Condition的等待池等待通知,并释放锁。使用前线程必须已获得锁定,否则将抛出异常。 
  notify(): 调用这个方法将从等待池挑选一个线程并通知,收到通知的线程将自动调用acquire()尝试获得锁定(进入锁定池);其他线程仍然在等待池中。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。 
  notifyAll(): 调用这个方法将通知等待池中所有的线程,这些线程都将进入锁定池尝试获得锁定。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。

典型应用示例:生产者-消费者模型

import threading
import time


class Huofu(threading.Thread):
    def __init__(self, name=None):
        threading.Thread.__init__(self)
        self.name = name

    def run(self):
        while True:
            cond.acquire()

            if len(guo) == 0:
                for i in range(1, 11):
                    guo.append(i)
                    print('做出第{0}个馒头'.format(i))
                    time.sleep(0.5)
                cond.notify_all()
                cond.release()
                cond2.acquire()  # 上锁
                cond2.wait()  # 等待
                cond2.release()  # 释放


class HeShang(threading.Thread):
    def __init__(self, name=None):
        threading.Thread.__init__(self)

        self.name = name

    def run(self):
        while True:
            mantou = None

            cond.acquire()
            if len(guo) == 0:
                cond2.acquire()
                cond2.notify()
                cond2.release()
                cond.wait()
            else:
                mantou = guo.pop()
            cond.release()
            if mantou is not None:
                print('{0}正在吃{1}'.format(self.name, mantou))
                time.sleep(0.5)


guo = []
lock = threading.Lock()
cond = threading.Condition(lock=lock)  # 吃的锁
lock2 = threading.Lock()
cond2 = threading.Condition(lock=lock2)  # 蒸馒头的锁
Huofu(name='做饭和尚').start()
HeShang(name='吃饭和尚1').start()
HeShang(name='吃饭和尚2').start()
HeShang(name='吃饭和尚3').start()
上面代码就是多线程共享一个数据,一个线程完成添加数据,另外三个线程消耗数据; 
当无数据时,Huofu线程会添加数据到固定数量,然后唤醒Chihuo进行数据消耗,等数据消耗完成之后再唤醒Huofu


优秀博文:



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值