百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

Python 3 高级编程 - 多线程编程 python多线程菜鸟教程

bigegpt 2024-12-19 11:31 4 浏览

python中的多线程是一个非常重要的知识点,python 默认是单线程的。

什么是线程:

  • 线程也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。
  • 线程是进程中的一个实体,是CPU调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。
  • 一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行

线程与进程的区别

进程(process)和线程(thread)是操作系统的基本概念,“进程是资源分配的最小单位,线程是CPU调度的最小单位”。线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。

进程是资源分配的基本单位。所有与该进程有关的资源,都被记录在进程控制块PCB中。以表示该进程拥有这些资源或正在使用它们。进程也是抢占处理机的调度单位,它拥有一个完整的虚拟地址空间。当进程发生调度时,不同的进程拥有不同的虚拟地址空间,而同一进程内的不同线程共享同一地址空间。

线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程的资源。线程只由相关堆栈(系统栈或用户栈)寄存器和线程控制表TCB组成。

通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。

主线程:当?个程序启动时,就有?个进程被操作系统(OS)创建,与此同时?个线程也?刻运 ?,该线程通常叫做程序的主线程。

主线程的重要性:

  • 是产生其他子线程的线程
  • 通常它必须最后完成执行各种关闭动作

子线程:可以看做是程序执?的?条分?,当?线程启动后会和主线程?起同时执?。

线程

一个线程有一个开始、一个执行顺序和一个结束。它有一个指令指针,可以跟踪它当前在上下文中的哪个位置运行。

  • 它可以被抢占(中断)。
  • 它可以在其他线程运行时暂时搁置(也称为休眠)——这称为让步。

有两种不同的线程:

  • kernel thread 内核线程
  • user thread 用户线程

内核线程是操作系统的一部分,而用户空间线程并未在内核中实现。

Python 提供了多个模块来支持多线程编程,包括 thread、 threading 和 Queue 模块等。程序是可以使用 thread 和 threading 模块来创建与管理线程。 thread 模块提供了基本的线程和锁定支持;而 threading 模块提供了更高级别、功能更全面的线程管理。

Python3 中有两个模块支持使用线程:

  • _thread
  • threading

在 Python 3 中,“thread”模块不再可用。为了在 Python3 中向后兼容,它已重命名为“_thread”。

创建一个新线程

要创建一个新线程,需要调用线程模块中的方法:

_thread.start_new_thread ( function, args[, kwargs] )

此方法调用支持在 Linux 和 Windows 中快速高效地创建新线程。方法调用立即返回,子线程启动并使用传递的参数列表调用函数。当函数返回时,线程终止。

args 是一个参数元组;使用空元组调用函数将不传递任何参数。 kwargs 是可选字典,是关键字参数。

例如:华罗庚先生的时间统筹法烧水,其实就是一个单线程和多线程的例子。

  • 如果按照线性思维,先去洗开水壶、烧水花去15分钟,再去洗茶壶、洗茶杯和拿茶叶要10分钟,那一共需要25分钟
  • 按照时间统筹法,先洗开水壶,把水放在炉子上烧,然后同时去洗茶壶、茶杯、拿茶叶,等水烧好了,茶具也准备好了,这样两件事情一共只需要花费15分钟,无形中你就节约了10分钟的时间。

单线程:

#单线程
import time

def boiling():
    print('我要烧开水了')
    time.sleep(40)    #模拟需要一定的时间
    print('水烧开了')

def wash_port():
    print('我要洗茶壶茶杯')
    time.sleep(20)
    print('茶壶洗好了')

def wash_cup():
    print('我要洗茶杯')
    time.sleep(10)
    print('茶杯洗好了')

start_time = time.time()
boiling()
wash_port()
wash_cup()
end_time = time.time()
print('总共耗时:{}'.format(end_time-start_time)) 

运行结果:

我要烧开水了
水烧开了
我要洗茶壶茶杯
茶壶洗好了
我要洗茶杯
茶杯洗好了
总共耗时:70.03596377372742

多线程:

import _thread
import time

def boiling():
    print('我要烧开水了')
    time.sleep(40)
    print('水烧开了')

def wash_port():
    print('我要洗茶壶茶杯')
    time.sleep(20)
    print('茶壶洗好了')

def wash_cup():
    print('我要洗茶杯')
    time.sleep(10)
    print('茶杯洗好了')

start_time = time.time()

try:
    _thread.start_new_thread( boiling,())
    _thread.start_new_thread( wash_port,())
    _thread.start_new_thread( wash_cup,())
except:
   print ("Error: unable to start thread")
finally:
    end_time = time.time()
    print('总共耗时:{}'.format(end_time-start_time)) 

while 1:   #一直等待,等待子线程运行完
   pass

运行结果:

总共耗时:0.0  #主线程,运行完了,子线程还在运行
我要烧开水了
我要洗茶杯
我要洗茶壶茶杯
茶杯洗好了
茶壶洗好了
水烧开了

程序进入无限循环。您必须按 ctrl-c 才能停止。

Threading 线程模块

Python 2.4 中包含的较新的线程模块为线程提供了比 _thread 线程模块更强大、更高级的支持。

threading 模块公开了 thread 模块的所有方法并提供了一些额外的方法:

  • threading.activeCount() - 返回活动线程对象的数量。
  • threading.currentThread() - 返回调用者线程控件中线程对象的数量。
  • threading.enumerate() - 返回当前活动的所有线程对象的列表。

threading 模块还有实现线程的Thread 类。 Thread 类提供的方法如下:

  • run() - 是线程的入口点。
  • start() - 通过调用 run 方法启动线程。
  • join([time]) - 等待线程终止。表示主线程等待子线程time时长(s)后子线程若还未结束,就强制结束子线程,不设置则主线程会一直等待子线程结束后再结束。
  • isAlive() - 检查线程是否仍在执行。
  • getName() - 返回线程的名称。
  • setName() - 设置线程的名称。
  • daemon 属性- 设置主,子线程运行时的关系。bool为True时主线程结束,子线程立即结束;为false主,子线程运行毫不相关,各自独立运行。

使用 threading 线程模块创建线程

创建线程之前,需要先把交给线程去做的工作写成一个函数,这个函数叫线程函数。

操作过程如下:

  • 定义 Thread 类的新子类。
  • 覆盖 __init__(self [,args]) 方法以添加额外的参数。
  • 然后,重写 run(self [,args]) 方法来实现线程在启动时应该做什么。

一旦创建了新的 Thread 子类,就可以创建它的一个实例,然后通过调用 start() 启动一个新线程,而 start() 又会调用 run() 方法。

import threading
import time

exitFlag = 0
#类必须继承threading.Thread
class myThread (threading.Thread):
	 #threadID, name, counter 为传入线程的参数,可根据自己的需求进行定义
   def __init__(self, threadID, name, counter):
      # Thread类的__init__(self)方法
      threading.Thread.__init__(self)
      self.threadID = threadID
      self.name = name
      self.counter = counter
   # 定义run()方法,主要写线程的执行内容
   def run(self):
      print ("Starting " + self.name)
      print_time(self.name, self.counter, 5)
      print ("Exiting " + self.name)

def print_time(threadName, delay, counter):   #线程函数
   while counter:
      if exitFlag:
         threadName.exit()
      time.sleep(delay)
      print ("%s: %s" % (threadName, time.ctime(time.time())))
      counter -= 1

# 创建两个线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# 启动线程
thread1.start()
thread2.start()
#等待线程终止
thread1.join()
thread2.join()
print ("Exiting Main Thread")

运行结果:

Starting Thread-1
Starting Thread-2
Thread-1: Mon Apr  3 11:42:02 2023
Thread-1: Mon Apr  3 11:42:03 2023
Thread-2: Mon Apr  3 11:42:03 2023
Thread-1: Mon Apr  3 11:42:04 2023
Thread-2: Mon Apr  3 11:42:05 2023
Thread-1: Mon Apr  3 11:42:05 2023
Thread-1: Mon Apr  3 11:42:06 2023
Exiting Thread-1
Thread-2: Mon Apr  3 11:42:07 2023
Thread-2: Mon Apr  3 11:42:09 2023
Thread-2: Mon Apr  3 11:42:11 2023
Exiting Thread-2   
Exiting Main Thread

注意:创建的线程为子线程,是主线程创建的子线程。

线程同步

Python 提供的线程模块包括一个易于实现的锁定机制,允许同步线程。通过调用 Lock() 方法创建一个新锁,该方法返回新锁。新锁对象的 acquire(blocking) 方法用于强制线程同步运行。可选的阻塞参数使您能够控制线程是否等待获取锁。如果阻塞设置为 0,则如果无法获取锁,线程将立即返回 0 值,如果获取了锁,则返回 1。如果 blocking 设置为 1,则线程阻塞并等待锁被释放。新锁对象的 release() 方法用于在不再需要时释放锁。

import threading
import time

class myThread (threading.Thread):
   def __init__(self, threadID, name, counter):
      threading.Thread.__init__(self)
      self.threadID = threadID
      self.name = name
      self.counter = counter
   def run(self):
      print ("Starting " + self.name)
      # 加锁进行线程同步
      threadLock.acquire()
      print_time(self.name, self.counter, 3)
      # 释放锁
      threadLock.release()

def print_time(threadName, delay, counter):
   while counter:
      time.sleep(delay)
      print ("%s: %s" % (threadName, time.ctime(time.time())))
      counter -= 1

threadLock = threading.Lock()
threads = []

# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# 启动线程
thread1.start()
thread2.start()

# 将线程添加到线程列表
threads.append(thread1)
threads.append(thread2)

# 等待所有线程运行完毕
for t in threads:
   t.join()
print ("Exiting Main Thread")

运行结果:

Starting Thread-1
Starting Thread-2
Thread-1: Mon Apr  3 11:51:44 2023
Thread-1: Mon Apr  3 11:51:45 2023
Thread-1: Mon Apr  3 11:51:46 2023
Thread-2: Mon Apr  3 11:51:48 2023
Thread-2: Mon Apr  3 11:51:50 2023
Thread-2: Mon Apr  3 11:51:52 2023
Exiting Main Thread   

多线程优先级队列

Queue 模块允许创建一个可以容纳特定数量项目的新队列对象。有以下方法来控制队列 -

  • get() - get() 从队列中删除并返回一个项目。
  • put() - put 将项目添加到队列中。
  • qsize() - qsize() 返回队列中当前的项目数。
  • empty() - 如果队列为空,则 empty() 返回 True;否则,假。
  • full() ? 如果队列已满,full() 返回 True;否则,假。
import queue
import threading
import time

exitFlag = 0

class myThread (threading.Thread):
   def __init__(self, threadID, name, q):
      threading.Thread.__init__(self)
      self.threadID = threadID
      self.name = name
      self.q = q
   def run(self):
      print ("Starting " + self.name)
      process_data(self.name, self.q)
      print ("Exiting " + self.name)

def process_data(threadName, q):
   while not exitFlag:
      queueLock.acquire()
      if not workQueue.empty():
         data = q.get()
         queueLock.release()
         print ("%s processing %s" % (threadName, data))
      else:
         queueLock.release()
         time.sleep(1)

threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = queue.Queue(10)
threads = []
threadID = 1

# Create new threads
for tName in threadList:
   thread = myThread(threadID, tName, workQueue)
   thread.start()
   threads.append(thread)
   threadID += 1

# Fill the queue
queueLock.acquire()
for word in nameList:
   workQueue.put(word)
queueLock.release()

# Wait for queue to empty
while not workQueue.empty():
   pass

# Notify threads it's time to exit
exitFlag = 1

# Wait for all threads to complete
for t in threads:
   t.join()
print ("Exiting Main Thread")

运行结果:

Starting Thread-1
Starting Thread-2
Starting Thread-3
Thread-1 processing One
Thread-2 processing Two
Thread-3 processing Three
Thread-1 processing Four
Thread-2 processing Five
Exiting Thread-3
Exiting Thread-1
Exiting Thread-2
Exiting Main Thread

守护线程

Daemon属性用于设置主线程的守护线程,在启动线程启动之前使用。当bool为True时,该线程为守护线程,主线程结束,子线程立即结束;为false主,子线程运行毫不相关,独立运行,子线程继续运行到结束。

例如,daemon = False:

import time
import threading

def sub_thread():
   print("子线程开始 ...")
   time.sleep(3)
   print("子线程结束 ...")

print("主线程开始 ...")
t = threading.Thread(target=sub_thread)
t.daemon = False
t.start()
print("主线程已结束 ...")

运行结果:

主线程开始 ...
子线程开始 ...  
主线程已结束 ...
子线程结束 ...

例如,daemon = True:

import time
import threading

def sub_thread():
   print("子线程开始 ...")
   time.sleep(3)
   print("子线程结束 ...")

print("主线程开始 ...")
t = threading.Thread(target=sub_thread)
t.daemon = True
t.start()
print("主线程已结束 ...")

运行结果:

主线程开始 ...
子线程开始 ...  
主线程已结束 ...

主线程结束时,子线程也立即被结束了。

线程锁

Lock锁

threading模块中Lock锁和_thread模块中的锁是一样的。

import threading
import time

num = 0
# 申请线程锁
lock = threading.Lock()
# 类必须继承threading.Thread


class threadTest(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    # 定义run()方法,主要写线程的执行内容
    def run(self):
        # 声明全局变量num
        global num
        # 申请线程锁
        lock.acquire()
        print('子线程' + self.name + '开始: ' +
              time.strftime('%T', time.localtime()))
        while num < 5:
            time.sleep(2)
            print(self.name, 'num: ', num)
            num += 1
        print('子线程' + self.name + '结束: ' +
              time.strftime('%T', time.localtime()))
        # 释放线程锁
        lock.release()
        return super().run()


print('主线程开始: %s' % (time.strftime('%T', time.localtime())))
thread1 = threadTest()
# 设置线程名称
thread1.name = 'Thread-1'
thread2 = threadTest()
# 设置线程名称
thread2.name = 'Thread-2'
# 启动线程
thread1.start()
# 启动线程
thread2.start()
time.sleep(1)
thread1.join()
thread2.join()
print('主线程已结束: %s' % (time.strftime('%T', time.localtime())))

运行结果:

主线程开始: 13:13:30
子线程Thread-1开始: 13:13:30
Thread-1 num:  0
Thread-1 num:  1
Thread-1 num:  2
Thread-1 num:  3
Thread-1 num:  4
子线程Thread-1结束: 13:13:40
子线程Thread-2开始: 13:13:40
子线程Thread-2结束: 13:13:40
主线程已结束: 13:13:40

在使用锁线程时,线程1和线程2对num操作就不会出现混乱。

RLock锁

RLock锁又称递归锁,其与Lock锁的差别在于,Lock锁只允许在同一线程中申请一次,否则线程会进入死锁,但是RLock允许在同一线程多次调用。
使用Lock锁产生死锁示例代码:

import threading
import time

print('主线程开始: %s' %(str(time.strftime('%T', time.localtime()))))
lock = threading.Lock()

# 申请线程锁
lock.acquire()
print(threading.enumerate())

# 再次申请线程锁,产生了死锁
lock.acquire()
print(threading.enumerate())

lock.release()
lock.release()

print('主线程结束: %s' %(str(time.strftime('%T',time.localtime()))))

运行结果:

主线程开始: 13:17:04
[<_MainThread(MainThread, started 31564)>]

线程锁死。

使用RLock锁不会产生死锁示例代码:

import threading
import time

print('主线程开始: %s' %(str(time.strftime('%T', time.localtime()))))

lock = threading.RLock()

# 申请线程锁
lock.acquire()
print(threading.enumerate())

# 再次申请线程锁,不会产生死锁
lock.acquire()
print(threading.enumerate())

lock.release()
lock.release()

print('主线程结束: %s' %(str(time.strftime('%T',time.localtime()))))

运行结果:

主线程开始: 13:19:07
[<_MainThread(MainThread, started 32932)>]
[<_MainThread(MainThread, started 32932)>]
主线程结束: 13:19:07

注意线程锁需要成对出现。

条件变量 Condition

条件变量(Condition)也是一把锁,除了同步锁的作用外,还具有在线程间通信的功能。  有一类线程需要满足条件之后才能够继续执行,Python提供了threading.Condition 对象用于条件变量线程的支持。

Condition是python3中一种更高级的锁,除和线程锁类似的 acquire() 和 release() 函数外,还提供以下函数:

  • wait() 条件不满足时调用,线程会释放锁并进入等待阻塞;
  • notify() 条件创造后调用,通知等待池激活一个线程;
  • notifyAll() 条件创造后调用,通知等待池激活所有线程。

注意:线程使用前需要获得锁,否则会抛出RuntimeError异常。

Condition提供了一种多线程通信机制,若线程1需要数据,线程1就会阻塞等待,线程2制造出数据,等待线程2制造好数据并通知线程1后,线程1就可以去获取数据了。

import threading,time
from random import randint

class Producer(threading.Thread):
    def run(self):
        global L
        while True:
            val=randint(0,10)
            print('生产者',self.name,":Append"+str(val),L)
            if lock_con.acquire():
                L.append(val)
                lock_con.notify()   #通知消费者,激活wait。
                lock_con.release()
            time.sleep(3)
            
class Consumer(threading.Thread):
    def run(self):
        global L
        while True:
            lock_con.acquire()  #wait阻塞后,从这里开始这行,重新获得锁。
            if len(L)==0:          #如果容器中没有数据,则等待。
                lock_con.wait()  #wait的作用:1、释放锁;2、阻塞,等待notify通知
            print('消费者',self.name,":Delete"+str(L[0]),L)
            del L[0]
            lock_con.release()
            time.sleep(1)

if __name__=="__main__":
    L=[]  #容器
    lock_con=threading.Condition()  #创建一把条件同步变量的锁。
    threads=[]
    for i in range(3):
        threads.append(Producer())
    threads.append(Consumer())
    for t in threads:
        t.start()       #start了4个线程对象。
    for t in threads:
        t.join()
    print('-------程序结束----------')

相关推荐

了解Linux目录,那你就了解了一半的Linux系统

大到公司或者社群再小到个人要利用Linux来开发产品的人实在是多如牛毛,每个人都用自己的标准来配置文件或者设置目录,那么未来的Linux则就是一团乱麻,也对管理造成许多麻烦。后来,就有所谓的FHS(F...

Linux命令,这些操作要注意!(linux命令?)

刚玩Linux的人总觉得自己在演黑客电影,直到手滑输错命令把公司服务器删库,这才发现命令行根本不是随便乱用的,而是“生死簿”。今天直接上干货,告诉你哪些命令用好了封神!喜欢的一键三连,谢谢观众老爷!!...

Linux 命令速查手册:这 30 个高频指令,拯救 90% 的运维小白!

在Linux系统的世界里,命令行是强大的武器。对于运维小白而言,掌握一些高频使用的Linux命令,能极大提升工作效率,轻松应对各种系统管理任务。今天,就为大家奉上精心整理的30个Linu...

linux必学的60个命令(linux必学的20个命令)

以下是Linux必学的20个基础命令:1.cd:切换目录2.ls:列出文件和目录3.mkdir:创建目录4.rm:删除文件或目录5.cp:复制文件或目录6.mv:移动/重命名文件或目录7....

提高工作效率的--Linux常用命令,能够决解95%以上的问题

点击上方关注,第一时间接受干货转发,点赞,收藏,不如一次关注评论区第一条注意查看回复:Linux命令获取linux常用命令大全pdf+Linux命令行大全pdf为什么要学习Linux命令?1、因为Li...

15 个实用 Linux 命令(linux命令用法及举例)

Linux命令行是系统管理员、开发者和技术爱好者的强大工具。掌握实用命令不仅能提高效率,还能解锁Linux系统的无限潜力,本文将深入介绍15个实用Linux命令。ls-列出目录内容l...

Linux 常用命令集合(linux常用命令全集)

系统信息arch显示机器的处理器架构(1)uname-m显示机器的处理器架构(2)uname-r显示正在使用的内核版本dmidecode-q显示硬件系统部件-(SMBIOS/DM...

Linux的常用命令就是记不住,怎么办?

1.帮助命令1.1help命令#语法格式:命令--help#作用:查看某个命令的帮助信息#示例:#ls--help查看ls命令的帮助信息#netst...

Linux常用文件操作命令(linux常用文件操作命令有哪些)

ls命令在Linux维护工作中,经常使用ls这个命令,这是最基本的命令,来写几条常用的ls命令。先来查看一下使用的ls版本#ls--versionls(GNUcoreutils)8.4...

Linux 常用命令(linux常用命令)

日志排查类操作命令查看日志cat/var/log/messages、tail-fxxx.log搜索关键词grep"error"xxx.log多条件过滤`grep-E&#...

简单粗暴收藏版:Linux常用命令大汇总

号主:老杨丨11年资深网络工程师,更多网工提升干货,请关注公众号:网络工程师俱乐部下午好,我的网工朋友在Linux系统中,命令行界面(CLI)是管理员和开发人员最常用的工具之一。通过命令行,用户可...

「Linux」linux常用基本命令(linux常用基本命令和用法)

Linux中许多常用命令是必须掌握的,这里将我学linux入门时学的一些常用的基本命令分享给大家一下,希望可以帮助你们。总结送免费学习资料(包含视频、技术学习路线图谱、文档等)1、显示日期的指令:d...

Linux的常用命令就是记不住,怎么办?于是推出了这套教程

1.帮助命令1.1help命令#语法格式:命令--help#作用:查看某个命令的帮助信息#示例:#ls--help查看ls命令的帮助信息#netst...

Linux的30个常用命令汇总,运维大神必掌握技能!

以下是Linux系统中最常用的30个命令,精简版覆盖日常操作核心需求,适合快速掌握:一、文件/目录操作1.`ls`-列出目录内容`ls-l`(详细信息)|`ls-a`(显示隐藏文件)...

Linux/Unix 系统中非常常用的命令

Linux/Unix系统中非常常用的命令,它们是进行文件操作、文本处理、权限管理等任务的基础。下面是对这些命令的简要说明:**文件操作类:*****`ls`(list):**列出目录内容,显...