python多线程与多进程

python多线程

python中提供两个标准库thread和threading用于对线程的支持,python3中已放弃对前者的支持,后者是一种更高层次封装的线程库,接下来均以后者为例。

创建线程

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....

方式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(['notepad'])

>>> 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锁。

GIL锁

提起python多线程就不得不提一下GIL(Global Interpreter Lock 全局解释器锁),这是目前占统治地位的python解释器CPython中为了保证数据安全所实现的一种锁。不管进程中有多少线程,只有拿到了GIL锁的线程才可以在CPU上运行,即时是多核处理器。对一个进程而言,不管有多少线程,任一时刻,只会有一个线程在执行。对于CPU密集型的线程,其效率不仅仅不高,反而有可能比较低。python多线程比较适用于IO密集型的程序。对于的确需要并行运行的程序,可以考虑多进程。

多线程对锁的争夺,CPU对线程的调度,线程之间的切换等均会有时间开销。

线程与进程区别

下面简单的比较一下线程与进程

进程是资源分配的基本单位,线程是CPU执行和调度的基本单位;

通信/同步方式:

进程:

通信方式:管道,FIFO,消息队列,信号,共享内存,socket,stream流;

同步方式:PV信号量,管程

线程:

同步方式:互斥锁,递归锁,条件变量,信号量

通信方式:位于同一进程的线程共享进程资源,因此线程间没有类似于进程间用于数据传递的通信方式,线程间的通信主要是用于线程同步。

CPU上真正执行的是线程,线程比进程轻量,其切换和调度代价比进程要小;

线程间对于共享的进程数据需要考虑线程安全问题,由于进程之间是隔离的,拥有独立的内存空间资源,相对比较安全,只能通过上面列出的IPC(Inter-Process Communication)进行数据传输;

系统有一个个进程组成,每个进程包含代码段、数据段、堆空间和栈空间,以及操作系统共享部分 ,有等待,就绪和运行三种状态;

一个进程可以包含多个线程,线程之间共享进程的资源(文件描述符、全局变量、堆空间等),寄存器变量和栈空间等是线程私有的;

操作系统中一个进程挂掉不会影响其他进程,如果一个进程中的某个线程挂掉而且OS对线程的支持是多对一模型,那么会导致当前进程挂掉;

如果CPU和系统支持多线程与多进程,多个进程并行执行的同时,每个进程中的线程也可以并行执行,这样才能最大限度的榨取硬件的性能;

线程和进程的上下文切换

进程切换过程切换牵涉到非常多的东西,寄存器内容保存到任务状态段TSS,切换页表,堆栈等。简单来说可以分为下面两步:

页全局目录切换,使CPU到新进程的线性地址空间寻址;

切换内核态堆栈和硬件上下文,硬件上下文包含CPU寄存器的内容,存放在TSS中;

线程运行于进程地址空间,切换过程不涉及到空间的变换,只牵涉到第二步;

使用多线程还是多进程?

CPU密集型:程序需要占用CPU进行大量的运算和数据处理;

I/O密集型:程序中需要频繁的进行I/O操作;例如网络中socket数据传输和读取等;

由于python多线程并不是并行执行,因此较适合与I/O密集型程序,多进程并行执行适用于CPU密集型程序;

python多线程和多进程的使用_python多线程与多进程相关推荐

  1. python多进程反而慢_python 3.6 多进程的坑

    python 慢是事实, 但是你如果想让他快,使用cpython 也可以,但是前提在你不使用第三方包的情况下,你要用了numpy pandas ,cython 就无法使用了,那只有 使用python的 ...

  2. python多线程编程技术主要应用_python多线程,多进程编程。

    进程,是目前计算机中为应用程序分配资源的最小单位: 线程,是目前计算机中运行应用程序的最小单位: 在实际系统中,其实进程都是被分为线程来实现的,所以参与时间片轮转的是线程: 但是管理应用程序的资源的单 ...

  3. python的多线程适合计算密集操作_Python 多线程操作学习

    Python 多线程操作 什么是线程: 线程(Thread)也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位.线程自己不拥有系统资源,只拥有一点儿在运行 ...

  4. python多线程读取文件的问题_Python多线程同步---文件读写控制方法

    1.实现文件读写的文件ltz_schedule_times.py #! /usr/bin/env python #coding=utf-8 import os def ReadTimes(): res ...

  5. python 银行业务系统程序编程写_python多线程实现代码(模拟银行服务操作流程)

    1.模拟银行服务完成程序代码 目前,在以银行营业大厅为代表的窗口行业中大量使用排队(叫号)系统,该系统完全模拟了人群排队全过程,通过取票进队.排队等待.叫号服务等功能,代替了人们站队的辛苦. 排队叫号 ...

  6. python多线程下载多个文件_python多线程下载文件 练习1

    ***今天在我BF的帮助下完成了小小的程序,也是我第一次写博客呢,谢谢他哦嘎嘎 1.首先,计算出下载文件的长度. conn = urllib.urlopen(url) headers = conn.i ...

  7. python多线程队列两组数据_python 多线程2-队列同步

    让我们考虑更复杂的一种场景:产品是各不相同的.这时只记录一个数量就不够了,还需要记录每个产品的细节.很容易想到需要用一个容器将这些产品记录下来. Python的Queue模块中提供了同步的.线程安全的 ...

  8. python重复运行程序百度知道_Python多线程采集百度相关搜索关键词工具带exe程序!...

    [Python] 纯文本查看 复制代码#百度相关搜索关键词抓取,读取txt关键词,导出txt关键词 # -*- coding=utf-8 -*- import requests import re i ...

  9. python多线程下载百度网盘_python多线程突破百度云限速下载

    1.首先得用网页打开你自己的百度云 2.点开你所要下载文件的下载链接,如下图 3.复制下载网址和文件名,自定义你想下载到的目录,填写到程序中 4.运行下列python程序 import request ...

最新文章

  1. hash 值重复_程序员:判断对象是否重复,不重写equals和hashcode不行吗?
  2. 新概念英语第一册1-34课单词
  3. Oracle 10g OCP认证资料(官方教材与考试题库)下载
  4. Centos7系统、Hadoop集群上部署ntp服务器
  5. 百度飞桨弯道超车了吗?!
  6. MFC 改变控件字体大小
  7. 数组的基本操作(1)
  8. Linux下正则表达式匹配性能
  9. 摩拜开锁系统是用什么服务器,摩拜昨晨现“开锁难” 市民:系统需更稳定
  10. vscode快速删除空白行方法
  11. windows 远程连接mongo_远程连接天下数据ADSL动态拨号VPS教程(windows)
  12. Centos7 安装teamviewer
  13. 读书笔记—情商:为什么情商比智商更重要
  14. 什么是Oncall?
  15. Python NLPIR(中科院汉语分词系统)的使用 十五分钟快速入门与完全掌握
  16. python中优先级最高的运算符为_Python 运算符优先级
  17. SSL网站安全证书https加密防劫持
  18. SpringSecurity如何处理logout注销操作
  19. Python爬取新冠肺炎疫情实时数据(丁香园)
  20. 模型蒸馏探索(Bert)

热门文章

  1. Python爬虫入门三之Urllib库的基本使用
  2. IOS 获取缓存目录文件大小并清除
  3. Android关于绘图中Shader 的效果(中级)
  4. jupyter Nodebook如何查看函数帮助
  5. 新手探索NLP(八)——序列标注
  6. 本地复现Zabbix v2.2.x, 3.0.0-3.0.3 jsrpc 参数 profileIdx2 SQL 注入漏洞
  7. 洛谷模拟赛 部落冲突
  8. bootstrapdatetimepicker 隐藏触发 bootstrap modal隐藏事件
  9. 编程面试过程中最常见的10大算法
  10. 双人五子棋对战(需要EasyX图像库)