什么是进程:

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

什么是线程:

线程是进程的一个实体,是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. LeetCode 25 —— K 个一组翻转链表

    1. 题目 2. 解答 首先,利用快慢指针确定链表的总结点数. 偶数个结点时,结点个数等于 i * 2. 奇数个结点时,结点个数等于 i * 2 + 1. 然后将链表的每 K 个结点划分为一组.循环对 ...

  2. Android之ViewPager 第二课

    在这里只粘贴部分代码 在第一课中,只有View滑动完毕,才触发动画效果,令滑块移动,在第二课中,将实现滑块与View同步运行. SecondActivity.java package com.andr ...

  3. 时间戳转换成日期的js

    在项目开发过程中,我们常常需要把时间戳转换成日期.下面这个是我一直使用的js方法,希望能帮助到有需要的朋友.大家如果有更好的方法,请多多指教! js代码如下: //时间戳转换成日期 function ...

  4. Java空指针异常解决方法

    Throwable是所有错误或异常的超类,只有当对象是这个类的实例时才能通过Java虚拟机或者Java throw语句抛出. 当Java运行环境发出异常时,会寻找处理该异常的catch块,找到对应的c ...

  5. C#中转义符

    C#转义字符: 一种特殊的字符常量:以反斜线"\"开头,后跟一个或几个字符.具有特定的含义,不同于字符原有的意义,故称“转义”字符.主要用来表示那些用一般字符不便于表示的控制代码. ...

  6. ARC078 D.Fennec VS. Snuke(树上博弈)

    题目大意: 给定一棵n个结点的树 一开始黑方占据1号结点,白方占据n号结点 其他结点都没有颜色 每次黑方可以选择黑色结点临近的未染色结点,染成黑色 白方同理. 最后谁不能走谁输. 题解: 其实简单想想 ...

  7. HDU 6194 string string string(后缀数组+RMQ)

    string string string Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Oth ...

  8. 面试前需要弄懂的SQL

    说明:创建数据库 view source   print? 1 Create DATABASE database-name 说明:删除数据库 view source   print? 1 drop d ...

  9. RTL2832U+R820T电视棒windows下安装sdr# 以及搭建ADS-B使用VirtualRadar看飞机的教程

    本文中提到的软件随后我会打包给出下载地址.这篇文章是我根据网上的教程和自己的经验修改的详细版本,为了方便入门新手.先来说说RTL2832U+R820T在windows下安装sdr#的方法.首先科普下s ...

  10. java实现极简的LRU算法

    import java.util.LinkedHashMap;import java.util.Map; /** * LRU (Least Recently Used)  */public class ...