Python 三种方法创建线程

当需要处理多任务时,多线程编程比起多进程编程往往更有优势,每一个进程都至少有一个线程,一个进程多个线程就可以实现多个任务。线程是操作系统直接支持的执行单元。许多高级语言都支持多线程编程,Python也提供了多个模块支持多线程编程,包括_thread、threading、Queue等模块。

而想要创建一个线程,则需要用到其中的_thread、或者threading模块。

创建线程

Python标准库中有两个模块提供对创建线程的支持,其中_thread模块是低级模块,功能十分有限。它在Python旧版本原叫做thread模块,后被更名为_thread,意为尽量在多线程编程中不要使用该模块。
而threading模块是高级模块,提供了丰富的多线程功能支持。
本篇文章主要介绍threading模块,对_thread模块感兴趣的可以私下了解。

创建线程的方法与创建进程的方法很像,创建进程时通过实例化multiprocessing模块下的Process对象,或者派生Process的子类,重写其run()方法创建进程,这两种方法与创建线程的方式非常像。
如果有兴趣的可以看我前两篇关于实现多进程的文章。

1.实例化Thread类创建线程

Thread类对象属性:

  • name :线程名。
  • ident :线程标识符。
  • daemon :布尔标志,表示这个线程是否是守护线程

Thread类的构造方法的参数如下:

Thread(self,group=None,target=None,name=None,args=(),kwargs={},*,daemon=None)
  • group :默认为None,该参数不必理会。
  • target :指定线程要调用的方法或函数。
  • name :指定线程的别名。
  • args :指定target所指向的方法或者函数要用到的位置参数。
  • keargs :指定target所指向的方法或者函数要用到的关键字参数。
  • daemon :指定线程是否为守护线程

示例:

import threading
def text():
    print('thread %s is running.' % (threading.current_thread().name)
    print('thread %s end.' % (threading.current_thread().name))
print('thread %s is running.' % (threading.current_thread().name))
t = threading.Thread(target=text)
t.start()
t.join()
print('thread %s end.' % (threading.current_thread().name)) 

运行结果:

thread MainThread is running.
thread Thread-1 is running.
thread Thread-1 end.
thread MainThread end.

<分析>

这个例子比较简单,只是简单的展示了创建线程的方法。每一个进程都有一个主线程,主线程是默认启动的,于是可以看到我们只实例化Thread类创建了一个线程,但运行结果却有两个线程。


另外可以发现每一个线程都有一个name,比方说MainThread(主线程)、Thread-1,这个name是哪来的呢?threading模块内有一个current_thread()函数,该函数总是返回当前线程的实例,那么current_thread().name不就是返回当前线程实例的name了么。另外,主线程默认的name就是MainThread,子线程如果没有在创建时指定,那么默认就是Thread-1、Thread-2…以此类推。


另外,启动一个线程的start()方法、用以表示现线程活动的run()方法、等待一个线程结束的join(timeout)方法,判断线程是否"存活"的is_alive()方法…这些方法都和进程中的这些方法完全类似,鉴于有关进程我已经在前两篇介绍过,这里就不再细说。


唯一提一点就是,进程中关于强制终止进程的terminate()方法不适用于Thread对象,唯独这一个不要在线程中去用。

最后,threading模块下除current_thread()之外的其他常用函数:

  • threading.enumerate() :返回一个包含正在运行的线程的list
  • threading.activeCount() :返回正在运行的线程数量。等价于len(threading.enumerate())

Thread类的一些其它方法:

  • getName() : 返回线程名。
  • setName() :设置线程名。
  • isDaemon() :判断该线程是否是守护线程,是返回True。
  • setDaemon() :设置线程的thread.daemon属性,该方法必须在线程start()之前调用。

以上方法都已经不常用,在Python的新版本中,设定name或者daemon属性可以不借助方法直接设定。

2.线程并发执行

进程和线程都可以并发执行,但线程由于划分尺度小,因此并发性相比于进程是要高的;另外,创建线程要更加简单,也更易于维护,由于多个线程共享进程的内存空间,这也使得线程更易于通信;除此之外,线程是Python直接支持的,这也简化了多线程的编程。

Python中的多线程也并不全是优点,事实上,Python中的多线程只是表面上的多线程,它和C++或者java中的不同,由于全局解释器锁GIL的存在,Python在一个时刻只会有一个线程在运行,这只是并发而不是并行。所以同样是多线程,java可以充分利用CPU的多核,但Python自始至终只会用到一个核,这就像单核CPU系统的多进程一样,Python的多线程是名不副实的。

这里展示一个多线程并发执行的例子:

import threading
from time import sleep
def text(n):
    for i in range(n):
        print('thread %s is running >> %d' % (threading.current_thread().name,i))
        if i == n-1:
            print('thread %s end.' % (threading.current_thread().name))
            break
        sleep(2)
print('thread %s is running.' % (threading.current_thread().name))
t1 = threading.Thread(target=text,args=(4,))
t2 = threading.Thread(target=text,args=(4,))
t1.start()
t2.start()
t1.join()
t2.join()
print('thread %s end' % (threading.current_thread().name))

运行结果:

thread MainThread is running.
thread Thread-1 is running >> 0
thread Thread-2 is running >> 0
thread Thread-2 is running >> 1
thread Thread-1 is running >> 1
thread Thread-2 is running >> 2
thread Thread-1 is running >> 2
thread Thread-2 is running >> 3
thread Thread-2 end.
thread Thread-1 is running >> 3
thread Thread-1 end.
thread MainThread end

<分析>

这个示例中我创建了两个子线程,并且每个子线程都调用text()函数,为了更好的体现并发,我在text()函数中设置了一个循环,按照我们以往程序顺序执行的思想,一个程序调用text()函数,循环没有结束那么该程序也将无法跳出循环。可是从运行结果中我们看到,线程一和线程二在交替执行,为了让这个效果放大,我在循环中加了个休眠,之后就正如我们发现的,两个线程似乎在同时进行一样,并不存在说一个线程结束之后才轮到下一个线程开始执行的情况。


这就是一个非常明显的多线程并发执行的例子。在执行并发时,CPU以快速轮换的方式交替执行每一个线程,从而给使用者一种错觉,即感觉像是所有线程在一起运行,实则不然,但所有的线程确实是在很短的时间内全部启动了。

3.继承Thread类创建线程

通过继承Thread,重写Thread类的run()方法得到Thread类的子类,实例化子类也能创建线程。

示例:

import threading
class Another_thread(threading.Thread):
    def __init__(self):
        super().__init__()
    def run(self):
        print('thread %s is running.' % (threading.current_thread().name))
        print('thread %s end.' % (threading.current_thread().name))
print('thread %s is running.' % (threading.current_thread().name))
t = Another_thread()
t.start()
t.join()
print('thread %s end.' % (threading.current_thread().name))

运行结果:

thread MainThread is running.
thread Thread-1 is running.
thread Thread-1 end.
thread MainThread end.

<分析>

如上,通过重写Thread类的run()方法,就可以在接下来直接使用Thread的子类创建一个线程,当然也可以重写Thread类的__init__()方法,得到一个令人满意、功能完备的Thread类的子类。这种创建线程的方式更加适合面向对象编程

4.实例化Thread,传入一个类实例

我们一般更倾向于使用上面介绍的两种方法创建线程,在第一种方法中,我们实例化Thread类,并传给它一个函数,第二种方法通过继承Thread得到子类创建线程,而接下来介绍的第三种方法,你会发现传入一个可调用的类实例同样可以创建线程。

这种创建线程的方法更加接近于面向对象的多线程编程,相比于传入函数而言具有更高的灵活性。

示例:

import threading
from time import sleep
loops = [1,2,3]
class MyThread(object):
    def __init__(self,func,args,name=''):
        self.name = name
        self.func = func
        self.args = args
    def __call__(self):
        self.func(*self.args)
def text(x,sp):
    print('thread %s is running >> %d.' % (threading.current_thread().name,x))
    sleep(sp)
    print('thread %s end.' % (threading.current_thread().name))
def main():
    print('START!')
    threads = []
    for i in range(3):
        t = threading.Thread(target=MyThread(text,(i,loops[i])))
        threads.append(t)
    for i in range(3):
        threads[i].start()
    for i in range(3):
        threads[i].join()
    print('ALL END!')
if __name__ == '__main__':
    main()

运行结果:

START!
thread Thread-1 is running >> 0.
thread Thread-2 is running >> 1.
thread Thread-3 is running >> 2.
thread Thread-1 end.
thread Thread-2 end.
thread Thread-3 end.
ALL END!

<分析>

这种创建线程的方法比起前两种应该稍微复杂了一点,但实际上也就是相比于传入函数改为传入一个类,类完全由我们自己定制,构造方法__init__()可以添加任何我们需要的参数,之后注意这个特殊方法__call__(),它调用传入类的函数,并使用我们传入类的参数。


如果我们力求逻辑清晰地快速创建一个线程,任务要求不高,我们倾向于使用第一种创建线程的方法;如果我们需要完全面向对象的多线程编程,我们倾向于使用继承Thread类、实例化子类创建线程的方法,所以这第三种方法的地位有点尴尬,它并不被常用,这里了解一下知道就好。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值