python进阶:Python进程、线程、队列、生产者/消费者模式、协程
一、进程和线程的基本理解
1、进程
程序是由指令和数据组成的,编译为二进制格式后在硬盘存储,程序启动的过程是将二进制数据加载进内存,这个启动了的程序就称作进程(可简单理解为进行中的程序)。例如打开一个QQ,word文档等程序,就会在内存中生成相应的进程,当然如上两种比较复杂的程序启动后由于有多个任务要进行处理,比如word在文字输入的同时还会进行拼写检查等,所以会由主进程fork为多个子进程,每个进程内可能还会由主线程生成有多个子线程。
那么操作系统如何并发执行多任务呢? 在以前的单核CPU时代,单个CPU会在多个进程间快速切换,比QQ进程执行了0.1秒,在word进程执行了0.2秒的时间分片,这种切换就是所谓的上下文切换,由于这种切换非常快,所以我们感觉是多个任务并行执行的,其实单核CPU是不可能实现真正的并行任务处理的。只有在多核CPU情况下,才可能实现真正的并行任务。(注意,只是可能,当并发任务数高于CPU核数(其实我们日常使用多数都是这种情况),多个CPU也是进行更复杂的上下文切换的。
2、线程
线程是最小的任务执行单元,此前博客中的Python代码都是单进程单线程的,每个进程至少有一个线程。
那么在多核CPU上想执行多个任务怎么办? 有如下两种解决方案:
1)多进程模式
2)多线程模式
当然也可以1)和2)并用,或者利用后面讲到的协程模式。
另外Python中由于GIL(全局解释器锁)的存在,是无法实现真正的多线程并行执行的,这也是Python多线程广为诟病的一点。本文后面聊到多线程的时候会展开解释。
小结:
进程之间由于独立开辟内存空间,所以数据是无法共享的,而是要通过IPC等方式进行进程间通信。
同一个进程内的多个线程之间是共享数据的,但是可能会产生dirty data,所以有了GIL的存在。
多任务间往往并不是独立的,而是可能有顺序和通信的需求,多进程、多线程编程有时是很复杂的,而且对于Python来说,多线程较为复杂,要尽量避免使用,而是通过进程池而得的多进程或协程来实现多任务并行。
二、多进程
优点:可以同时利用多个CPU。
缺点:每个进程开辟独立的内存空间,耗费资源。
场景:适用于计算密集型任务(相对于IO密集型任务,计算密集型任务对CPU占用较多。)
进程并不是越多越好的,大体上某任务的进程数量和多核CPU的数量相同为宜。
1、创建多进程
import multiprocessingimport osdef f1(a1): print(a1)if __name__=="__main__": print('Parent process %s.' % os.getpid()) t=multiprocessing.Process(target=f1,args=(11,)) #t.daemon = True #主进程不等待子进程 t.start() t2=multiprocessing.Process(target=f1,args=(11,)) t2.start() print('end')
代码如上,利用multiprocessing模块创建了两个进程,代码非常简单。
需要注意的是windows由于没有进程fork函数,不支持os.fork(),(与之相似的只有create process函数),所以上述代码块必须得在if __name__=="__main__":下执行才能成功。
t.daemon和t.join()都可以用来设置主进程是否等待子进程执行完再往下进行,通常用于进程间的同步。(一般情况下都要等待,否则子进程可能还没处理完任务就被主进程关闭)。
2、进程池
实际工作中需要创建多个进程一般都用进程池。(Python中自带了进程池,但没有线程池,需要要自己写,后面有示例。)
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。
Python中利用Pool来创建进程池。
from multiprocessing import Pool
import time
def Foo(i):
#time.sleep(1)
print(i)
return i+100
def Bar(arg):
print (arg)
#print pool.apply(Foo,(1,))
#print pool.apply_async(func =Foo, args=(1,)).get()
if __name__ == '__main__':
pool = Pool(5)
for i in range(10):
pool.apply_async(func=Foo, args=(i,),callback=Bar)
pool.close()
pool.join()#进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。#join方法里有断言(assert,所以不先close或terminate就会报错)
注意apply和apply_async的区别,后者包含了callback回调函数。 p = Pool(5)#创建5个进程池
p.apply #每一个任务是排队进行 ;每一个进程都有一个join方法
p.apply_async #每一个任务都并发执行,而且可以设置回调函数;进程无join方法,主进程没有等子进程,进程的daemon=True
再来看如下代码示例,进一步理解t.join()的功能:(代码转自廖雪峰Python教程)
def long_time_task(name):
print('Run task %s (%s)...' % (name, os.getpid()))
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print('Task %s runs %0.2f seconds.' % (name, (end - start)))
if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Pool(4)
for i in range(5):
p.apply_async(long_time_task, args=(i,))
print('Waiting for all subprocesses done...')
p.close()
p.join()
print('All subprocesses done.')
代码输出结果:
Parent process 15284. Waiting for all subprocesses done... Run task 0 (4420)... Run task 1 (540)... Run task 2 (6188)... Run task 3 (12060)... Task 1 runs 0.13 seconds. Run task 4 (540)... Task 0 runs 0.62 seconds. Task 2 runs 0.35 seconds. Task 3 runs 0.93 seconds. Task 4 runs 1.93 seconds. All subprocesses done.
可以看到task4等待其他任务都执行完了再执行。因为Pool的值是4,所以只能同时执行4个任务,第五个任务要等待其前某个任务执行完了空出来进程了再执行。所以上面改成pool(5)就看不到等待的任务了。另外pool的默认值等于你当前电脑的cpu数。
3、进程间共享数据
如前所述,进程间的数据是隔离的。
from multiprocessing import Process
from multiprocessing import Manager
import time
li = []
def foo(i):
li.append(i)
print ('say hi',li)
if __name__ == '__main__':
for i in range(10):
p = Process(target=foo,args=(i,))
p.start()
print ('ending',li)
输出结果:
ending [] say hi [0] say hi [1] say hi [2] say hi [4] ...
可以看到每个线程的数据都是独立的。
如果想让进程间数据共享有两种方式:(例如上面li列表有10个数据)
#方法1 Manager数据类型:
import os
from multiprocessing import Process
from multiprocessing import Manager
def foo(i,dic):
dic[i]=100+i
print(dic.values())
if __name__ == '__main__':
manage =Manager()#
dic = manage.dict()#为啥这俩放在外面就会报错 因为这是windows!~—~
#dic = {}
for i in range(2):
print('Parent process %s.' % os.getpid())
p=Process(target=foo,args=(i,dic))
print('Parent process %s.' % os.getpid())
p.start()
p.join()
#方法二,Array数据类型
from multiprocessing import Process,Array
temp = Array('i', [11,22,33,44])
def Foo(i):
temp[i] = 100+i
for item in temp:
print (i,'----->',item)
for i in range(2):
p = Process(target=Foo,args=(i,))
p.start()
4、进程间通信
进程间有时不但需要共享数据,还需要通信,操作系统提供了多种进程间通信机制,
Python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据。
我们以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据:
from multiprocessing import Process, Queue
import os, time, random
# 写数据进程执行的代码:
def write(q):
print('Process to write: %s' % os.getpid())
for value in ['A', 'B', 'C']:
print('Put %s to queue...' % value)
q.put(value)
time.sleep(random.random())
# 读数据进程执行的代码:
def read(q):
print('Process to read: %s' % os.getpid())
while True:
value = q.get(True)
print('Get %s from queue.' % value)
if __name__=='__main__':
# 父进程创建Queue,并传给各个子进程:
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
# 启动子进程pw,写入:
pw.start()
# 启动子进程pr,读取:
pr.start()
# 等待pw结束:
pw.join()
# pr进程里是死循环,无法等待其结束,只能强行终止:
pr.terminate()
在Unix/Linux下,multiprocessing模块封装了fork()调用,使我们不需要关注fork()的细节。由于Windows没有fork调用,因此,multiprocessing需要“模拟”出fork的效果,父进程所有Python对象都必须通过pickle序列化再传到子进程去,所以,如果multiprocessing在Windows下调用失败了,要先考虑是不是pickle失败了。
总结:
在Unix/Linux下,可以使用fork()调用实现多进程。
要实现跨平台的多进程,可以使用multiprocessing模块。
进程间通信是通过Queue、Pipes等实现的。
三、多线程
多任务可以由多进程完成,也可以由一个进程内的多个线程完成。
Python的标准库提供了两个支持多线程的模式:_thread(3.0之前是thread,3.0后改为_thread) 和threading模块,threading模块提供了更高级的机制,所以一般都用threading模块。
1. 创建多线程
创建线程很简单,就是用threading实例传入函数。
import time, threading
# 新线程执行的代码:
def loop():
print('thread %s is running...' % threading.current_thread().name)
n = 0
while n < 5:
n = n + 1
print('thread %s >>> %s' % (threading.current_thread().name, n))
time.sleep(1)
print('thread %s ended.' % threading.current_thread().name)
print('thread %s is running...' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print('thread %s ended.' % threading.current_thread().name)
上面的代码可以利用循环创建多个threading实例执行多个任务就实现了多线程。
import threading
import time
def show(arg):
time.sleep(1)
print( 'thread'+str(arg))
for i in range(10):
t = threading.Thread(target=show, args=(i,))
t.start()
print( 'main thread stop')
2. 线程锁
由于同一个进程内的多线程之间是数据共享的,而不像多进程间那样数据隔离,所以多个线程修改同一份数据,很可能会造成脏数据,因此就有了线程锁RLOCK。
此处要注意线程锁和GIL全局解释器锁的区别。 GIL是用来控制同时只有一个线程运行的,就像单核CPU的多进程那样。更多解释可以参见Python核心编程第三版中关于GIL的解释。而RLOCK是用来锁数据的。
线程锁代码示例:
import threading
import time
global_num = 0
lock = threading.RLock()
def Func():
lock.acquire()#获得锁
global global_num
global_num+=1
lock.release() #注意给线程解锁的位置
time.sleep(1)
print(global_num)
for i in range(10):
t = threading.Thread(target=Func)
t.start()
利用线程锁还要必要死锁的出现。
当多个线程同时执行lock.acquire()时,只有一个线程能成功地获取锁,然后继续执行代码,其他线程就继续等待直到获得锁为止。
获得锁的线程用完后一定要释放锁,否则那些苦苦等待锁的线程将永远等待下去,成为死线程。所以我们用try...finally来确保锁一定会被释放。
balance = 0
lock = threading.Lock()
def run_thread(n):
for i in range(100000):
# 先要获取锁:
lock.acquire()
try:
# 放心地改吧:
change_it(n)
finally:
# 改完了一定要释放锁:
lock.release()
锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。
threading.event
import threading
def do(test):
print('start 10 公里')
test.wait()
print('continue')
event_obj = threading.Event()
for i in range(10):
t = threading.Thread(target=do, args=(event_obj,))
t.start()
event_obj.clear()
Flag = input('请输入:')
if Flag == 'True':
event_obj.set()
3.自定义线程池
Python中自带了进程池,但没有线程池,所以需要自己写。
利用pool实现的简单版线程池:

#创建max_num个queue,再把threading.Thread类放入执行一个任务从queue中取走一个,然后再add一个。
import queue
import threading
class ThreadPool():
def __init__(self, max_num=20):
self.queue = queue.Queue(max_num)
for i in range(max_num):
self.queue.put(threading.Thread)
def get_thread(self):
return self.queue.get()
def add_thread(self):
self.queue.put(threading.Thread)
pool = ThreadPool(10)
def func(arg, p):
print (arg)
import time
time.sleep(2)
p.add_thread()
for i in range(30):
thread = pool.get_thread() #相当于threading.Thread
t = thread(target=func, args=(i, pool))
t.start()
简单版线程池

import queue
import threading
import contextlib
import time
StopEvent = object()
class ThreadPool(object):
def __init__(self, max_num, max_task_num = None):
if max_task_num:
self.q = queue.Queue(max_task_num)
else:
self.q = queue.Queue()
# 线程池最大容量
self.max_num = max_num
self.cancel = False
self.terminal = False
# 真实创建的线程列表
self.generate_list = []
# 空闲的线程列表
self.free_list = []
def run(self, func, args, callback=None):
"""
线程池执行一个任务
:param func: 任务函数
:param args: 任务函数所需参数
:param callback: 任务执行失败或成功后执行的回调函数,回调函数有两个参数1、任务函数执行状态;2、任务函数返回值(默认为None,即:不执行回调函数)
:return: 如果线程池已经终止,则返回True否则None
"""
if self.cancel:
return
if len(self.free_list) == 0 and len(self.generate_list) < self.max_num:
self.generate_thread()
w = (func, args, callback,)
self.q.put(w)
def generate_thread(self):
"""
创建一个线程
"""
t = threading.Thread(target=self.call)
t.start()
def call(self):
"""
循环去获取任务函数并执行任务函数
"""
# 获取当前线程
current_thread = threading.currentThread()
self.generate_list.append(current_thread)
event = self.q.get()
while event != StopEvent:
# 是元组是任务
# 解开任务包
# 执行任务
func, arguments, callback = event
try:
result = func(*arguments)
status = True
except Exception as e:
status = False
result = e
if callback is not None:
try:
callback(status, result)
except Exception as e:
pass
with self.worker_state(self.free_list, current_thread):
if self.terminal:
event = StopEvent
else:
event = self.q.get()
#优化前是以下三句代码
# self.free_list.append(current_thread)
# event = self.q.get()
# self.free_list.remove(current_thread)
else:
self.generate_list.remove(current_thread)
def close(self):
"""
执行完所有的任务后,所有线程停止
"""
self.cancel = True
full_size = len(self.generate_list)
while full_size:
self.q.put(StopEvent)
full_size -= 1
def terminate(self):
"""
无论是否还有任务,终止线程
"""
self.terminal = True
while self.generate_list:
self.q.put(StopEvent)
self.q.queue.clear() # 清空队列? self.q.empty()
@contextlib.contextmanager
def worker_state(self, state_list, worker_thread):
"""
用于记录线程中正在等待的线程数
"""
state_list.append(worker_thread)
try:
yield
finally:
state_list.remove(worker_thread)
# How to use
pool = ThreadPool(5)
def callback(status, result):
# status, execute action status
# result, execute action return value
pass
def action(i):
print(i)
for i in range(30):
ret = pool.run(action, (i,), callback)
time.sleep(5)
print(len(pool.generate_list), len(pool.free_list))
print(len(pool.generate_list), len(pool.free_list))
pool.close()
# pool.terminate()
高级版线程池
四、生产者、消费者模型
商品或服务的生产者生成商品,然后将其放到类似队列的数据结构中。生产者生产商品的时间是不确定的,消费者消费产品的时间也是不确定的。生产者消费者对立且并发的执行线程。
这种模型实际上是利用了Python的queue模块,提供线程间的通信。上面线程池的实现其实就是用了这种模型。

import queue
import threading
class ThreadPool():
def __init__(self, max_num=20):
self.queue = queue.Queue(max_num)
for i in range(max_num):
self.queue.put(threading.Thread)
def get_thread(self):
return self.queue.get()
def add_thread(self):
self.queue.put(threading.Thread)
pool = ThreadPool(10)
def func(arg, p):
print (arg)
import time
time.sleep(2)
p.add_thread()
for i in range(30):
thread = pool.get_thread() #相当于threading.Thread
t = thread(target=func, args=(i, pool))
t.start()
queue
五、协程
线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作则是程序员。
协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。
协程的适用场景:当程序中存在大量不需要CPU的操作时(IO),适用于协程;
协程是在一个线程执行过程中可以在一个子程序的预定或者随机位置中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。他本身是一种特殊的子程序或者称作函数。
应用:协程基于generator,Python3中内置了异步IO。遇到IO密集型的业务时,总是很费时间啦,多线程加上协程,你磁盘在那该读读该写写,我还能去干点别的。在WEB应用中效果尤为明显。
在“发出IO请求”到收到“IO完成”的这段时间里,同步IO模型下,主线程只能挂起,但异步IO模型下,主线程并没有休息,而是在消息循环中继续处理其他消息。这样,在异步IO模型下,一个线程就可以同时处理多个IO请求,并且没有切换线程的操作。对于大多数IO密集型的应用程序,使用异步IO将大大提升系统的多任务处理能力。
那和多线程比,协程有何优势?
最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
Python对协程的支持是通过generator实现的。
在generator中,我们不但可以通过for循环来迭代,还可以不断调用next()函数获取由yield语句返回的下一个值。
但是Python的yield不但可以返回一个值,它还可以接收调用者发出的参数。
Python中的协程经历了很长的一段发展历程。其大概经历了如下三个阶段:
- 最初的生成器变形yield/send
- Python3.4引入@asyncio.coroutine和yield from
- 在最近的Python3.5版本中引入async/await关键字
如下两个博客已经写的非常明确了:
http://blog.csdn.net/soonfly/article/details/78361819
http://python.jobbole.com/86069/
import asyncio
async def wget(host):
print('wget %s...' % host)
connect = asyncio.open_connection(host, 80)
reader, writer = await connect
header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host
writer.write(header.encode('utf-8'))
await writer.drain()
while True:
line = await reader.readline()
if line == b'\r\n':
break
print('%s header > %s' % (host, line.decode('utf-8').rstrip()))
# Ignore the body, close the socket
writer.close()
loop = asyncio.get_event_loop()
tasks = [wget(host) for host in ['www.sina.com.cn', 'www.sohu.com', 'www.iqiyi.com', 'www.youku.com']]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
协程是异步I/O的实现方式。
Python2.7中的实现方式:

from greenlet import greenlet
def test1():
print 12
gr2.switch()
print 34
gr2.switch()
def test2():
print 56
gr1.switch()
print 78
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
greenlet
greenlet

import gevent
def foo():
print('Running in foo')
gevent.sleep(0)
print('Explicit context switch to foo again')
def bar():
print('Explicit context to bar')
gevent.sleep(0)
print('Implicit context switch back to bar')
gevent.joinall([
gevent.spawn(foo),
gevent.spawn(bar),
])
gevent
gevent

from gevent import monkey; monkey.patch_all()
import gevent
import urllib2
def f(url):
print('GET: %s' % url)
resp = urllib2.urlopen(url)
data = resp.read()
print('%d bytes received from %s.' % (len(data), url))
gevent.joinall([
gevent.spawn(f, 'https://www.python.org/'),
gevent.spawn(f, 'https://www.yahoo.com/'),
gevent.spawn(f, 'https://github.com/'),
])
gevent 遇到IO操作自动切换
python进阶:Python进程、线程、队列、生产者/消费者模式、协程的更多相关文章
- .net学习之多线程、线程死锁、线程通信 生产者消费者模式、委托的简单使用、GDI(图形设计接口)常用的方法
1.多线程简单使用(1)进程是不执行代码的,执行代码的是线程,一个进程默认有一个线程(2)线程默认情况下都是前台线程,要所有的前台线程退出以后程序才会退出,进程里默认的线程我们叫做主线程或者叫做UI线 ...
- Java多线程-同步:synchronized 和线程通信:生产者消费者模式
大家伙周末愉快,小乐又来给大家献上技术大餐.上次是说到了Java多线程的创建和状态|乐字节,接下来,我们再来接着说Java多线程-同步:synchronized 和线程通信:生产者消费者模式. 一.同 ...
- day 28 :进程相关,进程池,锁,队列,生产者消费者模式
---恢复内容开始--- 前情提要: 一:进程Process 1:模块介绍 from multiprocessing import Process from multiprocessing impo ...
- python 多线程笔记(6)-- 生产者/消费者模式(续)
用 threading.Event() 也可以实现生产者/消费者模式 (自己拍脑袋想出来的,无法知道其正确性,请大神告知为谢!) import threading import time import ...
- 10 阻塞队列 & 生产者-消费者模式
原文:http://www.cnblogs.com/dolphin0520/p/3932906.html 在前面我们接触的队列都是非阻塞队列,比如PriorityQueue.LinkedList(Li ...
- python 多线程笔记(5)-- 生产者/消费者模式
我们已经知道,对公共资源进行互斥访问,可以使用Lock上锁,或者使用RLock去重入锁. 但是这些都只是方便于处理简单的同步现象,我们甚至还不能很合理的去解决使用Lock锁带来的死锁问题. 要解决更复 ...
- python自动化--语言基础线程、生产者消费者示例
进程与线程的区别:进程不共享空间,线程共享地址空间 线程共享空间优缺点:优点:多线程给用户的体验好些,打开时占用的内存比进程少缺点:共享地址空间会相互干扰,甚至有影响 import threading ...
- python进阶之 进程&线程区别
1.进程创建方式 import time import os from multiprocessing import Process def func (): time.sleep(1) print( ...
- python 全栈开发,Day39(进程同步控制(锁,信号量,事件),进程间通信(队列,生产者消费者模型))
昨日内容回顾 python中启动子进程并发编程并发 :多段程序看起来是同时运行的ftp 网盘不支持并发socketserver 多进程 并发异步 两个进程 分别做不同的事情 创建新进程join :阻塞 ...
随机推荐
- Python2.7-fractions
fractions 模块,提供分数格式存储数据,没多大用处,除了模块里的最大公约数函数 gcd(a,b) 模块类和方法: fractions.Fraction(numerator=0, denomin ...
- 蓝桥杯之大臣的旅费(两次dfs)
Description 很久以前,T王国空前繁荣.为了更好地管理国家,王国修建了大量的快速路,用于连接首都和王国内的各大城市. 为节省经费,T国的大臣们经过思考,制定了一套优秀的修建方案,使得任何一个 ...
- mysql show profiles 使用分析sql 性能
Show profiles是5.0.37之后添加的,要想使用此功能,要确保版本在5.0.37之后. 查看一下我的数据库版本 MySQL> Select version(); +-------- ...
- STS-创建spring配置文件
1.创建一个bean文件 2.输入文件名applicationContext.xml 3.这里会自动显示模板文件 4.创建后,自动填充头不定义 到这里就可以发现,我们创建spring文件时,需要的配置 ...
- 使用Novell.Directory.Ldap.NETStandard在.NET Core中验证AD域账号
Novell.Directory.Ldap.NETStandard是一个在.NET Core中,既支持Windows平台,又支持Linux平台,进行Windows AD域操作的Nuget包. 首先我们 ...
- CentOS 建立本地yum源服务器
安装CentOS系统,配置系统的网络环境 配置静态IP vi /etc/sysconfig/network-scripts/ifcfg-eth0 DEVICE=eth0 TYPE=Ethernet O ...
- 20155210 Exp7 网络欺诈防范
Exp7 网络欺诈防范 SET工具建立冒名网站 首先利用lsof -i:80或者netstat -tupln |grep 80查询80端口的使用情况(我的电脑80端口没有被占用,如果被占用,则用kil ...
- Android开发——监听Android手机的网络状态
0. 前言 在Android开发中监听手机的网络状态是一个常见的功能,比如在没网的状态下进行提醒并引导用户打开网络设置,或者在非wifi状态下开启无图模式等等.因此本篇将网上的资料进行了整理总结,方便 ...
- 9、Dockerfile实战-Nginx
上一节我们详解Dockerfile之后,现在来进行实战.我们通过docker build来进行镜像制作. build有如下选项: [root@localhost ~a]# docker build - ...
- 10、Dockerfile实战-PHP
一.镜像制作步骤 安装编译依赖包 编译安装 配置 二.编写Dockerfile FROM centos:7 MAINTAINER QUNXUE RUN yum install -y gcc gcc-c ...