什么是进程:

简单来讲,进程就是操作系统中运行的程序或任务,进程和程序的区别在于进程是动态的,而程序是静态的。进程是操作系统资源管理的最小单位。

什么是线程:

线程是进程的一个实体,是cpu调度和分派的最小单位,它是比进程更小的能独立运行的基本单位,线程本身不拥有资源,但它可以与同属于一个进程的线程共享进程的资源所拥有的全部资源。

python多线程编程与GIL:

为了更有效的利用多核处理,就出现了多线程编程,但是问题是线程间数据的一致性和状态的同步如果得到保证,因此python解析器引入了GIL全局锁
GIL全局锁的出现虽然保证了线程之间状态和一致性的原则,但是同一时间点上却只能有一个线程在运行。比如:我们有4核CPU,同时发起4个线程,每个
线程都在cpu上,但因为GIL全局锁的存在,在同一时间片上只有一个线程,所以多线程并发在python中就是一个美丽的梦。

线程与进程的区别:

1. 线程共享创建它的进程的地址空间;进程有自己的地址空间。
    2. 线程可以直接访问其进程的数据段;进程有自己的父进程数据段的副本。
    3. 新线程很容易创建;新进程需要父进程fork。
    4. 线程可以对同一进程的线程进行相当大的控制;进程只能对子进程执行控制。
    5. 对主线程的更改(取消、优先级变更等)可能会影响进程的其他线程的行为;对父进程的更改不会影响子进程。

python多进程模型

multiprocessing 是一个跨平台版本的多进程模块,multiprocessing模块提供了一个Process类来代表一个进程对象.

1. 进程的基本用法

#!_*_coding:utf-8_*_
# Author: hkey
from multiprocessing import Process # 导入Process方法
import os def run_proc():
print('child process run %s (%s)' %(os.getpid(), os.getppid())) # os.getpid获取当前进程的pid,os.getppid 获取当前进程的父进程pid if __name__ == '__main__':
print('parent process id: ', os.getpid())
p = Process(target=run_proc)
p.start()
p.join()
print('parent process %s done.' %os.getpid())

创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动。
join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步,当使用join()方法时就阻塞了主进程,直到子进程执行完毕,再次执行主进程

2. 进程池Pool

如果要启动大量子进程,可以用进程池的方式批量创建子进程:

#!_*_coding:utf-8_*_
# Author: hkey
from multiprocessing import Pool, Process
import os, time def run_proc():
print('Run task %s (%s)' %(os.getpid(), os.getppid()))
start_time = time.time()
time.sleep(1)
print('Task %s runs %.2f seconds.' %(os.getpid(), time.time()-start_time)) if __name__ == '__main__':
print('parent process %s' % os.getpid())
p = Pool(4)
for i in range(5):
p.apply_async(run_proc)
p.close()
p.join()
print('parent process %s done.' % os.getpid()) 输出结果: parent process 12980 Run task 8064 (12980)
Run task 9224 (12980)
Run task 11604 (12980)
Run task 13604 (12980) Task 8064 runs 1.00 seconds.
Run task 8064 (12980)
Task 9224 runs 1.00 seconds.
Task 11604 runs 1.00 seconds.
Task 13604 runs 1.00 seconds.
Task 8064 runs 1.00 seconds.
parent process 12980 done.

上面的例子,进程池最大限制4个子进程,但是循环了5次。从结果可以看到首先建立了4个子进程,当其中一个退出后,再次创建第5个子进程。
特别注意的是,在使用进程池的时候,首先要调用close()方法,调用close()方法后就不能继续添加新的子进程了,然后再调用join()方法。

3. 进程的锁lock

当多个进程需要访问共享资源的时候,Lock可以用来避免访问冲突。
    通过实例测试,在不加锁的情况下,多进程无论在读写同一个文件还是cpu计算都没有发生错误的现象。可能是我测试的量不够。

对文件写入:

#!_*_coding:utf-8_*_
#__author__:"hkey"
import multiprocessing, sys
def write1(f):
fs = open(f, 'a+')
n = 10000
while n > 0:
fs.write('aaaaaaaaaaaaaa\n')
n -= 1
fs.close() def write2(f):
fs = open(f, 'a+')
n = 10000
while n > 0:
fs.write('bbbbbbbbbbbbbbbb\n')
n -= 1
fs.close() if __name__ == '__main__':
f = 'test.txt'
p1 = multiprocessing.Process(target=write1, args=(f,))
p2 = multiprocessing.Process(target=write2, args=(f,))
p1.start()
p2.start() 多进程在没有加锁的情况下,没有出现写入错误的现象。 多进程加锁的写法: #!_*_coding:utf-8_*_
#__author__:"hkey"
import multiprocessing, sys
def write1(f, lock):
lock.acquire()
fs = open(f, 'a+')
n = 10000
while n > 0:
fs.write('aaaaaaaaaaaaaa\n')
n -= 1
fs.close()
lock.release()
def write2(f, lock):
lock.acquire()
fs = open(f, 'a+')
n = 10000
while n > 0:
fs.write('bbbbbbbbbbbbbbbb\n')
n -= 1
fs.close()
lock.release()
if __name__ == '__main__':
lock = multiprocessing.Lock()
f = 'test.txt'
p1 = multiprocessing.Process(target=write1, args=(f,lock))
p2 = multiprocessing.Process(target=write2, args=(f,lock))
p1.start()
p2.start()

个人总结:在多进程编程中,如果只是变量的计算或者cpu计算,可以不加锁,因为每个进程的地址空间都是独立的存在。
而在写入同一个文件的时候,就有必要加锁。

4. 子进程的控制

子进程是独立与主进程的存在,创建子进程时,系统fork出子进程后,就与主进程资源完全独立了,我们不单单创建完子进程就行了,还要控制子进程
    的输入和输出。
    
    subprocess模块可以让我们非常方便的启动一个子进程,然后控制其输入和输出。

子进程输出:
使用subprocess模块,在子进程中运行 ping -n 1 baidu.com

#!_*_coding:utf-8_*_
# Author: hkey
import subprocess print('$ ping -n 1 baidu.com')
r = subprocess.call(['ping', '-n', '', 'baidu.com'])
print('Exit code:', r) 输出结果: $ ping -n 1 baidu.com 正在 Ping baidu.com [111.13.101.208] 具有 32 字节的数据:
来自 111.13.101.208 的回复: 字节=32 时间=22ms TTL=51 111.13.101.208 的 Ping 统计信息:
数据包: 已发送 = 1,已接收 = 1,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 22ms,最长 = 22ms,平均 = 22ms
Exit code: 0 子进程输入: print('$nslookup')
p = subprocess.Popen(['nslookup'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, err = p.communicate(b'set q=mx\napache.org\nexit\n') # 通过调用communicate方法实现输入,这里是二进制格式。
result = output if output else err
print(result.decode('gbk'))
print('Exit code:', p.returncode) 输出结果:
# 主机dns有问题,但是输出结果完全正确的。
$nslookup
默认服务器: UnKnown
Address: 127.0.0.1 > > 服务器: UnKnown
Address: 127.0.0.1 >
Exit code: 0

5. 进程间通信

虽然子进程从主进程fork后是独立的存在,但进程之间肯定是需要通信的。python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等方式
来交换数据。
进程间通信,最典型的例子就是消费者生产者模型, 生产者生产数据,消费者消费。

#!_*_coding:utf-8_*_
# Author: hkey
from multiprocessing import Process, Queue
import os, time
def write(q):
for i in range(10):
print('process to write: %s' % os.getpid())
print('生产包子[%s]' %i)
q.put(i) # 将i上传至队列中。
time.sleep(1)
def read(q):
while True:
print('process to read: %s' % os.getpid())
values = q.get(True) # 通过get方法将队列中的数据下载,从队列中拿走一个数据就少一个数据。
print('吃掉包子[%s]' %values)
if __name__ == '__main__':
q = Queue() # 调用Queue方法生成一个队列
pw = Process(target=write, args=(q,)) # 通过进程的方式调用
pr = Process(target=read, args=(q,))
pw.start()
pr.start()
pw.join()
pr.terminate() # 因为read()是死循环,需要通过terminate()方法关闭。 输出结果: process to read: 16628
process to write: 16440
生产包子[0]
吃掉包子[0]
process to read: 16628
process to write: 16440
生产包子[1]
吃掉包子[1]
process to read: 16628
process to write: 16440
生产包子[2]
吃掉包子[2]
......

通过结果发现是两个进程通过队列在通信,生产一个包子,吃掉一个包子。

什么是生产者消费者模型

在python中,生产者消费者模型是一个很典型而且很经典的例子。
    队列的概念就是在生产者和消费者中间加一个类似仓库的中间层,生产者不在直接对应消费者,而是生产者将生产好的东西放置到仓库中,而当消费者需要
    的时候,自己去仓库中取出东西就好。这样做有以下几个优点:

1. 解耦
    2. 支持并发
    3. 支持忙闲不均

python多线程模型

多任务可以由多进程完成,也可以由一个进程内的多线程完成。一个进程中至少有一个线程。线程是操作系统直接支持的执行单元。
    threading模块提供python对线程的使用

1. 线程的基本用法

启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行,语法和进程差不多

#!_*_coding:utf-8_*_
# Author: hkey
import threading, time def run_thread():
print('thread %s running...' % threading.current_thread().name)
n = 0
while n < 5:
n += 1
print('thread %s >>> %s' % (threading.current_thread().name, n))
time.sleep(1)
print('thread %s ended.' % threading.current_thread().name) if __name__ == '__main__':
print('threading %s is running...' % threading.current_thread().name)
t = threading.Thread(target=run_thread, name='LoopThread')
t.start()
t.join()
print('thread %s ended.' % threading.current_thread().name) 输出结果: threading MainThread is running...
thread LoopThread running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended.
thread MainThread ended.

任何一个进程默认就有一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,python的threading模块有个current_thread()函数,
它永远返回当前线程的实例。主线程的名字叫 MainThread,子线程的名字在创建时指定,我们用LoopThread命名子线程。

2. 线程的型号量

相对于进程来说,线程占用的资源就很小,因此没有使用到线程池的概念,但是要实现类似线程池的功能可以使用线程的型号量来做限制
线程的信号量是同时允许一定数量的线程更改数据,主要作用在于限制线程的并发。

#!_*_coding:utf-8_*_
#__author__:"hkey"
import threading, time, os sem = threading.BoundedSemaphore(3) # 调用BoundedSemaphore方法限制3个线程的并发
def run():
sem.acquire() # 开始
print('threading running', threading.current_thread().name)
time.sleep(1)
sem.release() # 结束
if __name__ == '__main__':
for i in range(10):
t = threading.Thread(target=run)
t.start()

3. 线程的锁Lock

由于线程是共享进程的地址空间,所以在多线程的环境下,锁就显得尤为重要。多线程编程,在不加锁的情况下,同时对一个全局变量做修改,基本上全是错误。

#!_*_coding:utf-8_*_
#__author__:"hkey"
import threading
balance = 0
def run_thread(n):
global balance
for i in range(10000000):
balance = balance + n
balance = balance - n if __name__ == '__main__':
t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance) 输出结果:
-86

多线程编程加锁实例如下:

#!_*_coding:utf-8_*_
#__author__:"hkey"
import threading
balance = 0
def run_thread(lock, n):
lock.acquire()
global balance
for i in range(10000000):
balance = balance + n
balance = balance - n
lock.release()
if __name__ == '__main__':
lock = threading.Lock()
t1 = threading.Thread(target=run_thread, args=(lock, 5))
t2 = threading.Thread(target=run_thread, args=(lock, 8))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance) 输出结果:
0

多线程锁总结:在多线程编程中,无论是变量的计算还是写入同一个文件都要加锁,使用多线程编程一定要注意锁的使用。

[ Python - 14 ] python进程及线程编程的更多相关文章

  1. python中的进程、线程(threading、multiprocessing、Queue、subprocess)

    Python中的进程与线程 学习知识,我们不但要知其然,还是知其所以然.你做到了你就比别人NB. 我们先了解一下什么是进程和线程. 进程与线程的历史 我们都知道计算机是由硬件和软件组成的.硬件中的CP ...

  2. Python语法进阶(1)- 进程与线程编程

    1.进程与多进程 1.1.什么是进程 进程就是程序执行的载体 什么叫多任务? 多任务就是操作系统可以同时运行多个任务.比如你一边在用浏览器学习,还一边在听音乐,,这就是多任务,至少同时有3个任务正在运 ...

  3. Python 中的进程、线程、协程、同步、异步、回调

    进程和线程究竟是什么东西?传统网络服务模型是如何工作的?协程和线程的关系和区别有哪些?IO过程在什么时间发生? 一.上下文切换技术 简述 在进一步之前,让我们先回顾一下各种上下文切换技术. 不过首先说 ...

  4. 【廖雪峰老师python教程】——进程与线程

    多进程 操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去.表面上看,每个任务都是交替执行的,但是,由于CPU ...

  5. python基础之进程、线程、协程篇

    一.多任务(多线程) 多线程特点:(1)线程的并发是利用cpu上下文的切换(是并发,不是并行)(2)多线程执行的顺序是无序的(3)多线程共享全局变量(4)线程是继承在进程里的,没有进程就没有线程(5) ...

  6. Python之路-python(paramiko,进程和线程的区别,GIL全局解释器锁,线程)

    一.paramiko 二.进程.与线程区别 三.python GIL全局解释器锁 四.线程 语法 join 线程锁之Lock\Rlock\信号量 将线程变为守护进程 Event事件 queue队列 生 ...

  7. Python之路,进程、线程、协程篇

      本节内容 进程.与线程区别 cpu运行原理 python GIL全局解释器锁 线程 语法 join 线程锁之Lock\Rlock\信号量 将线程变为守护进程 Event事件 queue队列 生产者 ...

  8. Python自动化开发 - 进程、线程(一)

    本节内容 1.操作系统发展史 2.进程和线程 3.Python threading 模块 一.操系统发展史 手工操作(无操作系统) 1946年第一台计算机诞生--20世纪50年代中期,还未出现操作系统 ...

  9. Python学习--17 进程和线程

    线程是最小的执行单元,而进程由至少一个线程组成.如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间. 进程 fork调用 通过fork()系统调用,就可以生成一个子进程 ...

随机推荐

  1. Toward Convolutional Blind Denoising of Real Photographs

    本文提出了一个针对真实图像的盲卷积去噪网络,增强了深度去噪模型的鲁棒性和实用性. 摘要 作者提出了一个 CBD-Net,由噪声估计子网络和去噪子网络两部分组成. 作者设计了一个更加真实的噪声模型,同时 ...

  2. ThinkPHP自定义成功界面、失败界面、异常界面

    在ThinkPHP的手册中,附录里边的配置参考,有一个模板引擎设置. 或者在手册里面的控制器,跳转和重定向里面. 紧接着,就讲到了如何自定义这些界面. 将上诉的配置参数写到到配置文件里,修改路径到自己 ...

  3. vim使用注意事项

    vim使用注意事项 1. 中文编码的问题 中文编码有很多,如果文件与vim的终端界面使用的编码不同,那么在vim显示的文件内容将会是一堆乱码. 2. 语系编码转换 命令iconv可以将语系编码进行转换 ...

  4. Struts1之logic标签

    logic是Struts1中的逻辑标签 <%@ taglib prefix="logic" uri="http://struts.apache.org/tags-l ...

  5. The XOR Largest Pair

    刷刷书上的例题 在给定的N个整数A1,A2……An中选出两个进行XOR运算,得到的结果最大是多少?N<=105,0<=Ai<231 SOlution: 我们思考到对于两个数相异或,是 ...

  6. NAPT 分为锥型(Cone)和 对称型(Symmetric)

    NAPT 分为锥型(Cone)和 对称型(Symmetric) 链接:https://www.zhihu.com/question/38729355/answer/86531260 实际上大部运营商提 ...

  7. [SDOI2010]星际竞速——费用流

    类似于最短路的网络流,而且还要保证每个点经过一次,拆点就比较方便了. 连边怎么连?要保证最大流是n(每个点经过一次)还要能从直接跳转 将每个点拆点.源点向每个点的入点连一条容量为1费用为0的边.源点向 ...

  8. makefile使用笔记(一)入门

    By francis_hao    Mar 2,2017 makefile makefile一个很简单的例子如下,该实例完成在执行make时,将main.c编译成可执行文件main的功能. 各项的含义 ...

  9. Codeforces 931.D Peculiar apple-tree

    D. Peculiar apple-tree time limit per test 1 second memory limit per test 256 megabytes input standa ...

  10. Failed to resolve:com.android.support:appcompat-v7

    http://blog.csdn.net/mhl18820672087/article/details/78385361