Python3 多线程

真正意义上的多线程是由CPU来控制的,例如如果一个CPU密集型的程序,用C语言写,运行在一个四核处理器上,采用多线程的话最多可以获得4倍的效率提升。但是用Python写的话,效率不会提高,甚至会变慢,因为Python中的多线程是由GIL控制的,GIL的全称是Global Interpreter Lock(全局解释器锁),Python最初的设计理念在于,为了解决多线程之间数据完整性和状态同步的问题,设计为在任意时刻只能由一个线程在解释器中运行。因此Python中的多线程是表面上的多线程(同一时刻只有一个线程),不是真正的多线程。除了程序本身执行外,还多了线程切换所花的时间。因此,Python多线程相对更适合写I/O密集型的程序,真正对效率要求高的CPU密集型程序都用C/C++去写

Python3 线程中常用的两个模块为:

  • _thread
  • threading(推荐使用)

一、_thread 模块

_thread 提供了低级别的、原始的线程以及一个简单的锁,它相比于 threading 模块的功能还是比较有限的。函数式:调用 _thread 模块中的start_new_thread()函数来产生新线程。语法如下:

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

参数说明:

  • function - 线程函数。
  • args - 传递给线程函数的参数,他必须是个tuple类型。
  • kwargs - 可选参数。
#!/usr/bin/python3

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: 无法启动线程")

while 1:
   pass

执行以上程序输出结果如下:

Thread-1: Wed Apr  6 11:36:31 2016
Thread-1: Wed Apr  6 11:36:33 2016
Thread-2: Wed Apr  6 11:36:33 2016
Thread-1: Wed Apr  6 11:36:35 2016
Thread-1: Wed Apr  6 11:36:37 2016
Thread-2: Wed Apr  6 11:36:37 2016
Thread-1: Wed Apr  6 11:36:39 2016
Thread-2: Wed Apr  6 11:36:41 2016
Thread-2: Wed Apr  6 11:36:45 2016
Thread-2: Wed Apr  6 11:36:49 2016

二、threading

threading 模块除了包含 _thread 模块中的所有方法外,还提供的其他方法:

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

除了使用方法外,threading 模块同样提供了Thread类来处理线程,​​​​​​其构造函数原型如下:

threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)

下面是Thread的参数说明

  • group:默认为None(该参数是为了以后实现ThreadGroup类而保留的)
  • target:在run方法中调用的可调用对象,即需要开启线程的可调用对象,比如函数或方法。
  • name:线程名称,默认为“Thread-N”形式的名称,N为较小的十进制数。
  • args:在参数target中传入的可调用对象的参数元组,默认为空元组()。
  • kwargs:在参数target中传入的可调用对象的关键字参数字典,默认为空字典{}。
  • daemon:默认为None,即继承当前调用者线程(即开启线程的线程,一般就是主线程)的守护模式属性,如果不为None,则无论该线程是否为守护模式,都会被设置为“守护模式”。

Thread类还提供了以下方法:

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

我们可以使用threading 模块的Thread类直接创建一个线程:

import time
import random
import threading


def func(name):
    s = random.randint(1, 5)
    print(f'current thread is {name}, sleeping {s}s.')
    time.sleep(s)
    print(f'thread {name} is over')


if __name__ == '__main__':
    for i in range(1, 5):
        t = threading.Thread(target=func, args=(i,))
        t.start()
    print('Main Thread')

也可以可以通过直接从 threading.Thread 继承创建一个新的子类,并实例化后调用 start() 方法启动新线程:

#!/usr/bin/python3

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 ("开始线程:" + self.name)
        print_time(self.name, self.counter, 5)
        print ("退出线程:" + 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 ("退出主线程")

以上程序执行结果如下:

开始线程:Thread-1
开始线程:Thread-2
Thread-1: Wed Apr  6 11:46:46 2016
Thread-1: Wed Apr  6 11:46:47 2016
Thread-2: Wed Apr  6 11:46:47 2016
Thread-1: Wed Apr  6 11:46:48 2016
Thread-1: Wed Apr  6 11:46:49 2016
Thread-2: Wed Apr  6 11:46:49 2016
Thread-1: Wed Apr  6 11:46:50 2016
退出线程:Thread-1
Thread-2: Wed Apr  6 11:46:51 2016
Thread-2: Wed Apr  6 11:46:53 2016
Thread-2: Wed Apr  6 11:46:55 2016
退出线程:Thread-2
退出主线程

线程同步

如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。

使用 Thread 对象的 Lock 和 Rlock 可以实现简单的线程同步,这两个对象都有 acquire 方法和 release 方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire 和 release 方法之间。

python线程池的使用

在过去,我们可以使用第三方模块threadpool来创建线程池。但是现在主流的使用线程池的模块是python3中自带的模块concurrent.futures模块中的ThreadPoolExecutor,如果对threadpool感兴趣的小伙伴可以自行搜索相关的信息。

下面主要介绍一下ThreadPoolExecutor的使用方法,创建一个最大容纳数量为3的线程池对象t,通过submit提交执行的函数到线程池中,但线程池中的某个线程(thread 1)执行完成,则把空闲的线程(thread 4)放入到池子中,直到所有线程执行完成则程序结束。:

import time
import random
from concurrent.futures import ThreadPoolExecutor


def func(name):
    s = random.randint(1, 5)
    print(f'current thread is {name}, sleeping {s}s.')
    time.sleep(s)
    print(f'thread {name} is over')


if __name__ == '__main__':
    with ThreadPoolExecutor(max_workers=3) as t:
        for i in range(1, 6):
            t.submit(func, i)

执行结果如下:

current thread is 1, sleeping 1s.
current thread is 2, sleeping 1s.
current thread is 3, sleeping 2s.
thread 1 is over
current thread is 4, sleeping 2s.
thread 2 is over
current thread is 5, sleeping 4s.
thread 3 is over
thread 4 is over
thread 5 is over

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Chiang木

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值