Python多线程

多线程

线程,有时被称为轻量进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程有三种基本状态:就绪、阻塞和运行。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。

线程是程序中一个单一的顺序控制流程。进程内有一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指令运行时的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程

多线程:多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理(Chip-level multithreading)或同时多线程(Simultaneous multithreading)处理器。 在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理(Multithreading)”。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程(台湾译作“执行绪”),进而提升整体处理性能

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

Python通过两个标准库thread和threading提供对线程的支持。thread提供了低级别的、原始的线程以及一个简单的锁。

threading 模块提供的其他方法:

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

除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:

  • run(): 用以表示线程活动的方法。
  • start():启动线程活动。
  • join([time]): 等待至线程中止。
  • isAlive(): 返回线程是否活动的。
  • getName(): 返回线程名。
  • setName(): 设置线程名。

1.线程创建

Thread(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None))

参数:

​ target:线程执行体

​ args:任意位置参数

​ kwargs:任意关键字参数

import threading
from random import randint
from time import sleep

def run(name):
    print("{}线程开始".format(name))
    sleep(randint(1,3))
    print("{}线程结束".format(name))

if __name__ == "__main__":
    print("主线程:{}".format(threading.current_thread().name))
    t1 = threading.Thread(target=run,args=("线程1",))
    t1.start()
    t1.join()
    print("主线程结束")
  • 线程之间数据共享

    多线程和多进程最大的不同在于,多进程中,同一个全局变量,每个子进程各自有一份拷贝,互不影响。而在多线程中,所有变量都由线程共享,所以任何一个变量都可以被任意线程所修改。因此,多个线程同时改变一个变量,容易把内容改乱了

    import threading
    import random
    import time
    
    money = 1000
    
    def run1():
        global money
        for i in range(100):
            money -= 1
    
    def run2():
        global money
        for i in range(100):
            money -= 1
    
    if __name__ == "__main__":
        #创建线程
        th1 = threading.Thread(target=run1, name="th1")
        th2 = threading.Thread(target=run2, name="th2")
    
        #启动
        th1.start()
        th2.start()
    
        #等待子线程结束
        th1.join()
        th2.join()
    
        print("money = %d"%money)
    
  • 定义自己的线程类

    
    

2.线程同步

import threading
import random
import time

list1 = [0]*10
def run1():
    global list1
    #获取线程锁,如果已上锁,则阻塞等待锁的释放
    for i in range(len(list1)):
        list1[i] = 1
        time.sleep(0.2)
def run2():
    global list1
    for i in range(len(list1)):
        print(list1[i],end=' ')
        time.sleep(0.2)
if __name__ == "__main__":
    #创建线程
    th1 = threading.Thread(target=run1,  name="th1")
    th2 = threading.Thread(target=run2,  name="th2")

    #启动
    th1.start()
    th2.start()

    #等待子线程结束
    th1.join()
    th2.join()
    print("\nlist1 = {}".format(list1))

问题产生的原因就是没有控制多个线程对同一资源的访问,对数据造成破坏,使得线程运行的结果不可预期。这种现象称为“线程不安全”。

如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。使用Thread对象的Lock和Rlock可以实现简单的线程同步,这两个对象都有acquire方法和release方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到acquire和release方法之间。

多线程的优势在于可以同时运行多个任务(至少感觉起来是这样)。但是当线程需要共享数据时,可能存在数据不同步的问题。为了避免这种情况,引入了锁的概念。

import threading
import random
import time
#线程锁
lock = threading.Lock()

list1 = [0]*10

def run1():
    global list1

    #获取线程锁,如果已上锁,则阻塞等待锁的释放
    lock.acquire()
    for i in range(len(list1)):
        list1[i] = 1
        time.sleep(0.2)
    lock.release()

def run2():
    global list1
    with lock:
        for i in range(len(list1)):
            print(list1[i],end=' ')
            time.sleep(0.2)

if __name__ == "__main__":
    #创建线程
    th1 = threading.Thread(target=run1,  name="th1")
    th2 = threading.Thread(target=run2,  name="th2")

    #启动
    th1.start()
    th2.start()

    #等待子线程结束
    th1.join()
    th2.join()

    print("\nlist1 = {}".format(list1))
  • ThreadLocal

    一个ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰。ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题

    import threading
    #创建全局对象global_data
    global_data = threading.local()
    def show():
        print(threading.current_thread().getName(), global_data.num)
    def thread_cal():
    	# num是当前线程的局部变量
        global_data.num = 0
        for _ in range(1000):
            global_data.num += 1
        show()
    
    if __name__ == "__main__":
        threads = []
        for i in range(10):
            threads.append(threading.Thread(target=thread_cal))
            threads[i].start()
        for i in range(10):
            threads[i].join()
        print("Main thread: ", global_data.__dict__ ) # {}
    
    
  • 生产者和消费者

Python的Queue模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue。这些队列都实现了锁原语(可以理解为原子操作,即要么不做,要么就做完),能够在多线程中直接使用。可以使用队列来实现线程间的同步。

import threading
import queue
import random
import time

def produce(q):
    i = 0
    while i < 10:
        num = random.randint(1, 100)
        q.put("生产者产生数据:%d"%num)
        print("生产者产生数据:%d"%num)
        time.sleep(1)
        i += 1
    q.put(None)
    # 完成任务
    q.task_done()


def consume(q):
    while True:
        item = q.get()
        if item is None:
            break
        print("消费者获取到:%s"%item)
        time.sleep(4)
    # 完成任务
    q.task_done()


if __name__ == "__main__":
    q = queue.Queue(10)
    arr = []

    #创建生产者
    th = threading.Thread(target=produce, args=(q, ))
    th.start()

    # 创建消费者
    tc = threading.Thread(target=consume, args=(q, ))
    tc.start()


    th.join()
    tc.join()
    print("END")
  • 条件变量

    有些复杂问题互斥锁搞不定了。Python提供的Condition对象提供了对复杂线程同步问题的支持。Condition被称为条件变量,除了提供与Lock类似的acquirerelease方法外,还提供了waitnotify方法。

    import threading
    # 可传入一个互斥锁或者可重入锁
    cond = threading.Condition()
    实例方法:
    acquire([timeout])/release(): 线程锁/释放锁 
    wait([timeout]):  线程挂起,直到收到一个notify通知或者超时(可选的,浮点数,单位是秒s)
                     才会被唤醒继续运行。wait()必须在已获得Lock前提下才能调用,否则会触发RuntimeError。 
    notify(n=1): 通知其他线程,那些挂起的线程接到这个通知之后会开始运行,
    		默认是通知一个正等待该condition的线程,最多则唤醒n个等待的线程。notify()必须在已获得Lock前提
    		下才能调用,否则会触发RuntimeError。notify()不会主动释放Lock。
    notifyAll(): 通知所有线程,这些线程都将进入锁定池尝试获得锁定。
                 调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。
    
    import threading
    import time
    
    con = threading.Condition()
    num = 0  #鱼丸数量
    
    # 生产者
    def produce():
        # 锁定线程
        global num
        con.acquire()
        while True:
            print("开始添加!!!")
            num += 1
            print("火锅里面鱼丸个数:%s" % str(num))
            time.sleep(1)
            if num >= 5:
                print("火锅里面里面鱼丸数量已经到达5个,无法添加了!")
                # 唤醒等待的线程
                con.notify()  # 唤醒小伙伴开吃啦
                # 等待通知
                con.wait()
        # 释放锁
        con.release()
    
    # 消费者
    def  consume():
        con.acquire()
        global num
        while True:
            print("开始吃啦!!!")
            num -= 1
            print("火锅里面剩余鱼丸数量:%s" % str(num))
            time.sleep(2)
            if num <= 0:
                print("锅底没货了,赶紧加鱼丸吧!")
                con.notify()  # 唤醒其它线程
                # 等待通知
                con.wait()
        con.release()
    if __name__ == "__main__":
        p = threading.Thread(target=produce)
        c = threading.Thread(target=consume)
        p.start()
        c.start()
        p.join()
        c.join()
    

3.定时线程

import time
import threading
def run():
    print("啦啦,啦啦,我是卖报的小行家")
if __name__ == "__main__":
    # 进程启动5秒后在执行,不会阻塞主线程
    th = threading.Timer(5, run)
    th.start()
    th.join()

4.进程和线程的区别

进程是系统进行资源分配和调度的一个独立单位.

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.

  • 区别:
    • 一个程序至少有一个进程,一个进程至少有一个线程.
    • 线程的划分尺度小于进程(资源比进程少),使得多线程程序的并发性高。
    • 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
    • 线线程不能够独立执行,必须依存在进程中
  • 优缺点
    • 线程执行开销小,但不利于资源的管理和保护;而进程正相反。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值