Python学习-多线程

1. 线程

首先区分线程和进程两个概念:

  • 进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。
  • 线程是一条执行路径,是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。

线程与进程是有区别的,每个独立的进程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。

指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。

比如谷歌浏览器本身是一个进程,在浏览器中打开一个网页,这就是一个线程,同时打开多个网页,分别用来看资料,玩游戏,翻译文档等,这就是多线程。

线程的优点:

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

2.线程函数及模块

Python中使用线程有两种方式:函数或者用类来包装线程对象。

函数式:调用thread模块中的start_new_thread()函数来产生新线程。

语法如下:

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

python3略有不同 

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

 也可以这样写:

start_new(function, args, kwargs=None)

参数说明:

  • function - 线程函数。
  • args - 传递给线程函数的参数,他必须是个tuple类型(不可更改)。
  • kwargs - 可选参数。

线程的结束可以调用thread.exit();

例如:

import _thread
import time


def print_time(threadName, delay):
    count = 0
    while count < 5:
        time.sleep(delay)
        count += 1
        print("%s: %s" % (threadName, time.ctime(time.time())))

try:
    _thread.start_new_thread(print_time, ("Thread-1", 2,))
    _thread.start_new_thread(print_time, ("Thread-2", 4,))
except:
    print("Error: unable to start thread")

while 1:
    pass

输出:

Thread-1: Sun Aug  2 09:26:05 2020
Thread-1: Sun Aug  2 09:26:07 2020
Thread-2: Sun Aug  2 09:26:07 2020
Thread-1: Sun Aug  2 09:26:09 2020
Thread-1: Sun Aug  2 09:26:11 2020
Thread-2: Sun Aug  2 09:26:11 2020
Thread-1: Sun Aug  2 09:26:13 2020
Thread-2: Sun Aug  2 09:26:15 2020
Thread-2: Sun Aug  2 09:26:19 2020
Thread-2: Sun Aug  2 09:26:23 2020

运行过程:一共创建了两个线程,线程1和线程2,线程1每次休眠2s,线程2每次休眠4s,开始先创建线程1,打印时间05,经过2s,打印线程1时间,并且现在经过了两个线程1,也就是4s,创建线程2并打印时间09,接下来的4s内继续打印两个线程1的时间,然后过了8s,第二次打印线程2的时间,再到10s时打印线程1的最后一次时间,经过2s打印线程2的第三次时间,最后每隔4s打印一次线程2的时间,最后5次线程1和线程2的时间都打印了出来。

 

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

threading 模块提供的其他方法:

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

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

  • run(): 用以表示线程活动的方法。
  • start():启动线程活动。
  • join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
  • isAlive(): 返回线程是否活动的。
  • getName(): 返回线程名。
  • setName(): 设置线程名。
import threading
import time

exitFlag = 0


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)
        print_time(self.name, self.counter, 5)
        print("Exiting " + self.name)



def print_time(threadName, delay, counter):
    while counter:
        if exitFlag:
            (threading.Thread).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()

print("Exiting Main Thread")
Starting Thread-1
Starting Thread-2
Exiting Main Thread
Thread-1: Sun Aug  2 15:14:36 2020
Thread-2: Sun Aug  2 15:14:37 2020
Thread-1: Sun Aug  2 15:14:37 2020
Thread-1: Sun Aug  2 15:14:38 2020
Thread-1: Sun Aug  2 15:14:39 2020
Thread-2: Sun Aug  2 15:14:39 2020
Thread-1: Sun Aug  2 15:14:40 2020
Exiting Thread-1
Thread-2: Sun Aug  2 15:14:41 2020
Thread-2: Sun Aug  2 15:14:43 2020
Thread-2: Sun Aug  2 15:14:45 2020
Exiting Thread-2

Process finished with exit code 0

开始定义了一个myThread类继承于threading.Thread,并进行初始化,在run函数中先输出一个开始的提示语句,然后调用输出时间函数,并传入参数。时间函数作用是打印当前进程的名称和时间。首先实例化了2个对象thread1,thread2,然后调用start函数开启线程,先开启的是thread1,因此先打印的是thread1的线程开始提示语句,然后是thread2的,接下来输出Exit Main Thread,结束主线程,然后每隔1秒打印一次thread1,每隔2s打印一次thread2,打印完5次后输出Exiting提示语句表明线程完成,当前线程完成。

3. 线程同步

一个进程中往往会有多个线程,当多个进程同时对某一个数据进行修改时就会发生错误,Thread对象的Lock和Rlock可以实现简单的线程同步,这两个对象都有acquire方法和release方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到acquire和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)

        # 获得锁,成功获得锁定后返回True
        # 可选的timeout参数不填时将一直阻塞直到获得锁定
        # 否则超时后将返回False
        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: Sun Aug  2 16:25:55 2020
Thread-1: Sun Aug  2 16:25:56 2020
Thread-1: Sun Aug  2 16:25:57 2020
Thread-2: Sun Aug  2 16:25:59 2020
Thread-2: Sun Aug  2 16:26:01 2020
Thread-2: Sun Aug  2 16:26:03 2020
Exiting Main Thread

可以看见,通过lock锁让一个线程能够使一个进程先运行完再执行另外一个线程,保证在一个线程的执行过程中不会被打扰。

4.线程优先级队列

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

Queue模块中的常用方法:

  • Queue.qsize() 返回队列的大小
  • Queue.empty() 如果队列为空,返回True,反之False
  • Queue.full() 如果队列满了,返回True,反之False
  • Queue.full 与 maxsize 大小对应
  • Queue.get([block[, timeout]])获取队列,timeout等待时间
  • Queue.get_nowait() 相当Queue.get(False)
  • Queue.put(item) 写入队列,timeout等待时间
  • Queue.put_nowait(item) 相当Queue.put(item, False)
  • Queue.task_done() 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号
  • Queue.join() 实际上意味着等到队列为空,再执行别的操作

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值