个人一直觉得对学习任何知识而言,概念是相当重要的。掌握了概念和原理,细节可以留给实践去推敲。掌握的关键在于理解,通过具体的实例和实际操作来感性的体会概念和原理可以起到很好的效果。本文通过一些具体的例子简单介绍一下Python的多线程和多进程,后续会写一些进程通信和线程通信的一些文章。
 
python多线程
python中提供两个标准库thread和threading用于对线程的支持,python3中已放弃对前者的支持,后者是一种更高层次封装的线程库,接下来均以后者为例。
 
创建线程
ASP站长网python中有两种方式实现线程:
 
实例化一个threading.Thread的对象,并传入一个初始化函数对象(initial function )作为线程执行的入口;
继承threading.Thread,并重写run函数;
方式1:创建threading.Thread对象
import threading
import time
 
def tstart(arg):
    time.sleep(0.5)
    print("%s running...." % arg)
 
if __name__ == '__main__':
    t1 = threading.Thread(target=tstart, args=('This is thread 1',))
    t2 = threading.Thread(target=tstart, args=('This is thread 2',))
    t1.start()
    t2.start()
    print("This is main function")
结果:
 
This is main function
This is thread 2 running....
This is thread 1 running....
View Code
方式2:继承threading.Thread,并重写run
import threading
import time
 
class CustomThread(threading.Thread):
    def __init__(self, thread_name):
        # step 1: call base __init__ function
        super(CustomThread, self).__init__(name=thread_name)
        self._tname = thread_name
 
    def run(self):
        # step 2: overide run function
        time.sleep(0.5)
        print("This is %s running...." % self._tname)
 
if __name__ == "__main__":
    t1 = CustomThread("thread 1")
    t2 = CustomThread("thread 2")
    t1.start()
    t2.start()
    print("This is main function")
 执行结果同方式1.
 
threading.Thread
上面两种方法本质上都是直接或者间接使用threading.Thread类
 
threading.Thread(group=None, target=None, name=None, args=(), kwargs={})
 
关联上面两种创建线程的方式:
 
import threading
import time
 
class CustomThread(threading.Thread):
    def __init__(self, thread_name, target = None):
        # step 1: call base __init__ function
        super(CustomThread, self).__init__(name=thread_name, target=target, args = (thread_name,))
        self._tname = thread_name
 
    def run(self):
        # step 2: overide run function
        # time.sleep(0.5)
        # print("This is %s running....@run" % self._tname)
        super(CustomThread, self).run()
 
def target(arg):
    time.sleep(0.5)
    print("This is %s running....@target" % arg)
 
if __name__ == "__main__":
    t1 = CustomThread("thread 1", target)
    t2 = CustomThread("thread 2", target)
    t1.start()
    t2.start()
    print("This is main function")
结果:
 
This is main function
This is thread 1 running....@target
This is thread 2 running....@target
上面这段代码说明:
 
两种方式创建线程,指定的参数最终都会传给threading.Thread类;
传给线程的目标函数是在基类Thread的run函数体中被调用的,如果run没有被重写的话。
threading模块的一些属性和方法可以参照官网,这里重点介绍一下threading.Thread对象的方法
 
下面是threading.Thread提供的线程对象方法和属性:
 
start():创建线程后通过start启动线程,等待CPU调度,为run函数执行做准备;
run():线程开始执行的入口函数,函数体中会调用用户编写的target函数,或者执行被重载的run函数;
join([timeout]):阻塞挂起调用该函数的线程,直到被调用线程执行完成或超时。通常会在主线程中调用该方法,等待其他线程执行完成。
name、getName()&setName():线程名称相关的操作;
ident:整数类型的线程标识符,线程开始执行前(调用start之前)为None;
isAlive()、is_alive():start函数执行之后到run函数执行完之前都为True;
daemon、isDaemon()&setDaemon():守护线程相关;
这些是我们创建线程之后通过线程对象对线程进行管理和获取线程信息的方法。
 
 多线程执行
 在主线程中创建若线程之后,他们之间没有任何协作和同步,除主线程之外每个线程都是从run开始被执行,直到执行完毕。
 
join
 
我们可以通过join方法让主线程阻塞,等待其创建的线程执行完成。
 
import threading
import time
 
def tstart(arg):
    print("%s running....at: %s" % (arg,time.time()))
    time.sleep(1)
    print("%s is finished! at: %s" % (arg,time.time()))
 
if __name__ == '__main__':
    t1 = threading.Thread(target=tstart, args=('This is thread 1',))
    t1.start()
    t1.join()   # 当前线程阻塞,等待t1线程执行完成
    print("This is main function at:%s" % time.time())
结果:
 
This is thread 1 running....at: 1564906617.43
This is thread 1 is finished! at: 1564906618.43
This is main function at:1564906618.43
如果不加任何限制,当主线程执行完毕之后,当前程序并不会结束,必须等到所有线程都结束之后才能结束当前进程。
 
将上面程序中的t1.join()去掉,执行结果如下:
 
This is thread 1 running....at: 1564906769.52
This is main function at:1564906769.52
This is thread 1 is finished! at: 1564906770.52
可以通过将创建的线程指定为守护线程(daemon),这样主线程执行完毕之后会立即结束未执行完的线程,然后结束程序。
 
deamon守护线程
 
import threading
import time
 
def tstart(arg):
    print("%s running....at: %s" % (arg,time.time()))
    time.sleep(1)
    print("%s is finished! at: %s" % (arg,time.time()))
 
if __name__ == '__main__':
    t1 = threading.Thread(target=tstart, args=('This is thread 1',))
    t1.setDaemon(True)
    t1.start()
    # t1.join()   # 当前线程阻塞,等待t1线程执行完成
    print("This is main function at:%s" % time.time())
结果:
 
This is thread 1 running....at: 1564906847.85
This is main function at:1564906847.85
python多进程
相比较于threading模块用于创建python多线程,python提供multiprocessing用于创建多进程。先看一下创建进程的两种方式。
 
The multiprocessing package mostly replicates the API of the threading module.  —— python doc
 
创建进程
 
创建进程的方式和创建线程的方式类似:
 
实例化一个multiprocessing.Process的对象,并传入一个初始化函数对象(initial function )作为新建进程执行入口;
继承multiprocessing.Process,并重写run函数;
方式1:
from multiprocessing import Process  
import os, time
 
def pstart(name):
    # time.sleep(0.1)
    print("Process name: %s, pid: %s "%(name, os.getpid()))
 
if __name__ == "__main__": 
    subproc = Process(target=pstart, args=('subprocess',))  
    subproc.start()  
    subproc.join()
    print("subprocess pid: %s"%subproc.pid)
    print("current process pid: %s" % os.getpid())
结果:
 
Process name: subprocess, pid: 4888 
subprocess pid: 4888
current process pid: 9912
方式2:
from multiprocessing import Process  
import os, time
 
class CustomProcess(Process):
    def __init__(self, p_name, target=None):
        # step 1: call base __init__ function()
        super(CustomProcess, self).__init__(name=p_name, target=target, args=(p_name,))
 
    def run(self):
        # step 2:
        # time.sleep(0.1)
        print("Custom Process name: %s, pid: %s "%(self.name, os.getpid()))
 
if __name__ == '__main__':
    p1 = CustomProcess("process_1")
    p1.start()
    p1.join()
    print("subprocess pid: %s"%p1.pid)
    print("current process pid: %s" % os.getpid())
这里可以思考一下,如果像多线程一样,存在一个全局的变量share_data,不同进程同时访问share_data会有问题吗?
 
由于每一个进程拥有独立的内存地址空间且互相隔离,因此不同进程看到的share_data是不同的、分别位于不同的地址空间,同时访问不会有问题。这里需要注意一下。
 
Subprocess模块
 
既然说道了多进程,那就顺便提一下另一种创建进程的方式。
 
python提供了Sunprocess模块可以在程序执行过程中,调用外部的程序。
 
如我们可以在python程序中打开记事本,打开cmd,或者在某个时间点关机:
 
>>> import subprocess
>>> subprocess.Popen(['cmd'])
<subprocess.Popen object at 0x0339F550>
>>> subprocess.Popen(['notepad'])
<subprocess.Popen object at 0x03262B70>
>>> subprocess.Popen(['shutdown', '-p'])
或者使用ping测试一下网络连通性:
 
>>> res = subprocess.Popen(['ping', 'www.cnblogs.com'], stdout=subprocess.PIPE).communicate()[0]
>>> print res
正在 Ping www.cnblogs.com [101.37.113.127] 具有 32 字节的数据:
来自 101.37.113.127 的回复: 字节=32 时间=1ms TTL=91
来自 101.37.113.127 的回复: 字节=32 时间=1ms TTL=91
来自 101.37.113.127 的回复: 字节=32 时间=1ms TTL=91
来自 101.37.113.127 的回复: 字节=32 时间=1ms TTL=91
 
101.37.113.127 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 1ms,最长 = 1ms,平均 = 1ms
 
python多线程与多进程比较
先来看两个例子:
 
开启两个python线程分别做一亿次加一操作,和单独使用一个线程做一亿次加一操作:
 
def tstart(arg):
    var = 0
    for i in xrange(100000000):
        var += 1
 
if __name__ == '__main__':
    t1 = threading.Thread(target=tstart, args=('This is thread 1',))
    t2 = threading.Thread(target=tstart, args=('This is thread 2',))
    start_time = time.time()
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print("Two thread cost time: %s" % (time.time() - start_time))
    start_time = time.time()
    tstart("This is thread 0")
    print("Main thread cost time: %s" % (time.time() - start_time))
结果:
 
Two thread cost time: 20.6570000648
Main thread cost time: 2.52800011635
上面的例子如果只开启t1和t2两个线程中的一个,那么运行时间和主线程基本一致。这个后面会解释原因。
 
使用两个进程进行上面的操作:
 
def pstart(arg):
    var = 0
    for i in xrange(100000000):
        var += 1
 
if __name__ == '__main__':
    p1 = Process(target = pstart, args = ("1", ))
    p2 = Process(target = pstart, args = ("2", ))
    start_time = time.time()
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    print("Two process cost time: %s" % (time.time() - start_time))
    start_time = time.time()
    pstart("0")
    print("Current process cost time: %s" % (time.time() - start_time))
结果:
 
Two process cost time: 2.91599988937
Current process cost time: 2.52400016785
对比分析
 
双进程并行执行和单进程执行相同的运算代码,耗时基本相同,双进程耗时会稍微多一些,可能的原因是进程创建和销毁会进行系统调用,造成额外的时间开销。
 
但是对于python线程,双线程并行执行耗时比单线程要高的多,效率相差近10倍。如果将两个并行线程改成串行执行,即:
 
    t1.start()
    t1.join()
    t2.start()
    t2.join()
    #Two thread cost time: 5.12199997902
    #Main thread cost time: 2.54200005531
可以看到三个线程串行执行,每一个执行的时间基本相同。
 
本质原因双线程是并发执行的,而不是真正的并行执行。原因就在于GIL锁。

dawei

【声明】:九江站长网内容转载自互联网,其相关言论仅代表作者个人观点绝非权威,不代表本站立场。如您发现内容存在版权问题,请提交相关链接至邮箱:bqsm@foxmail.com,我们将及时予以处理。