Day034--Python--锁, 信号量, 事件, 队列, 生产者消费者模型, joinableQueue
进程同步:
1. 锁 (重点)
锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁
Lock 先异步, 到共同区域的时候同步, 一次只能有一个进程执行加锁的程序, 避免错乱. 由并发变成串行, 牺牲效率, 保证了数据的安全.
- import json
- from multiprocessing import Process
- def get_ticket(i):
- with open('ticket', 'r') as f: # 此时的读取信息时异步的, 可能出现进程1打开的时候进程2已经把票数修改了
- last_ticket_info = json.load(f)
- last_ticket = last_ticket_info['count']
- if last_ticket > 0:
- last_ticket = last_ticket - 1
- last_ticket_info['count'] = last_ticket
- with open('ticket', 'w') as f: # 涉及到文件修改的地方现在还是异步的
- json.dump(last_ticket_info, f)
- print('恭喜%s号用户,抢票成功!' % i)
- else:
- print('%s号用户,票已经抢光啦~' % i)
- if __name__ == '__main__':
- for i in range(1, 11):
- p = Process(target=get_ticket, args=(i,))
- p.start()
- # 这样可能会出现全都抢到票的情况, 因为票数0还没来得及写入文件
- # test文件
- {"count": 0} # 一会儿要用json, 只能用双引号
json中只能用双引号" "
- # 通过加锁解决问题, 只有一个客户能抢到票
- import time
- import random
- import json
- from multiprocessing import Process, Lock
- def get_ticket(i, ticket_lock):
- # 加锁, 确保每次只有一个进程执行锁里面的程序, 这一段程序对于所有写上这个锁的进程来说, 大家都变成了串行
- ticket_lock.acquire() # 被锁住的程序运行变成了同步运行, 只能是串行, 降低了效率
- with open('ticket', 'r') as f:
- time.sleep(random.random()) # 模拟网络延迟, 0-1内随机小数
- ticket_info = json.load(f)
- ticket_num = ticket_info['count']
- if ticket_num > 0:
- ticket_info['count'] = ticket_num - 1
- with open('ticket', 'w') as f:
- time.sleep(random.random()) # 模拟网络延迟, 0-1内随机小数
- json.dump(ticket_info, f)
- print('恭喜%s号用户,抢票成功!' % i)
- else:
- print('很遗憾, %s号用户, 票抢光了~~' % i)
- ticket_lock.release() # 解锁, 解锁之后其他进程才能继续执行自己的程序
- if __name__ == '__main__':
- ticket_lock = Lock() # 创建一把锁
- lst = []
- for i in range(1, 11):
- p = Process(target=get_ticket, args=(i, ticket_lock))
- p.start()
- lst.append(p)
- # 抢完一波赶紧把票补上, 继续抢下一波~
- for p in lst:
- p.join()
- with open('ticket', 'r') as f:
- ticket_info = json.load(f)
- ticket_info["count"] = 3 # 一次补货3张票
- with open('ticket', 'w') as f:
- json.dump(ticket_info, f)
使用同步锁写一个简单的抢票程序,提供并发查票和并发买票的功能
- import json
- import time
- import random
- from multiprocessing import Process, Lock
- def check(i):
- time.sleep(random.random())
- with open('ticket', 'r') as f:
- ticket_info = json.load(f)
- ticket_num = ticket_info['count']
- print('用户%s查看剩余票数: %s张' % (i, ticket_num))
- return ticket_info
- def get_ticket(lock, i):
- lock.acquire()
- time.sleep(random.random()) # 模拟网络延迟
- with open('ticket', 'r') as f:
- ticket_info = json.load(f)
- ticket_num = ticket_info['count']
- if ticket_num > 0:
- with open('ticket', 'w') as f:
- ticket_info['count'] = ticket_num - 1
- json.dump(ticket_info, f)
- print('用户%s抢票成功!' % i)
- else:
- print('很遗憾,用户%s没有抢到票, 明年再来吧.' % i)
- lock.release()
- if __name__ == '__main__':
- lock = Lock()
- clst = []
- glst = []
- for i in range(1, 11): # 创建用户
- c_p = Process(target=check, args=(i,))
- c_p.start()
- clst.append(c_p)
- g_p = Process(target=get_ticket, args=(lock, i))
- g_p.start()
- glst.append(g_p)
- for c in clst:
- c.join()
- for g in glst:
- g.join()
- with open('ticket', 'r') as f:
- ticket_info = json.load(f)
- ticket_info['count'] = 1
- with open('ticket', 'w') as f:
- json.dump(ticket_info, f)
作业
2. 信号量
Semaphore
- 互斥锁同时只允许一个线程更改数据,而信号量Semaphore是同时允许一定数量的线程更改数据 。
- 假设商场里有4个迷你唱吧,所以同时可以进去4个人,如果来了第五个人就要在外面等待,等到有人出来才能再进去玩。
- 实现:
- 信号量同步基于内部计数器,每调用一次acquire(),计数器减1;每调用一次release(),计数器加1.当计数器为0时,acquire()调用被阻塞。这是迪科斯彻(Dijkstra)信号量概念P()和V()的Python实现。信号量同步机制适用于访问像服务器这样的有限资源。
- 信号量与进程池的概念很像,但是要区分开,信号量涉及到加锁的概念
信号量
- '''
- 10个客户去按摩店,按摩店有4个服务房间,每个客人独占1间房,其余人排队等候. 每个人按摩的时间不一样,
- 只有当一个客户按摩完成走出房间,给出空房信号,另一位等候的客户才能进入房间.
- '''
- import time
- import random
- from multiprocessing import Process, Semaphore
- def message(i,s):
- s.acquire() # lock开始计数, 每个进程占用1把锁, 相当于每个客人独占一个房间
- t = random.randrange(1, 5) # 每个个人按摩时间不一样, 随机前闭后开1-4秒
- print('%s号客人正在按摩中...还需等待%s秒' % (i, t))
- time.sleep(t) # 按摩中...
- print('%s号顾客离开了房间' % i)
- s.release() # 锁释放了, lock计数-1, 有空房间了, 可以服务下一个顾客
- if __name__ == '__main__':
- s = Semaphore(4) # 一共4把锁, 相当于4个房间
- lst = []
- for i in range(1, 11): # 客人编号1-10
- p = Process(target=message, args=(i, s))
- p.start()
- lst.append(p)
- for p in lst:
- p.join()
- print('客人全部离开, 打烊中...')
3. 事件
Event
- python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。
- 事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。
- clear:将“Flag”设置为False
- set:将“Flag”设置为True
事件
- from multiprocessing import Process, Event
- e = Event() # 创建一个事件
- print(e.is_set()) # 查看事件状态, bool, 初始状态 False
- e.set() # 将e事件的状态改为True
- print('在这里等待')
- e.clear() # 将e事件的状态改为False
- print('等待中...')
- e.wait() # 只有e时间状态为True时, 才能继续往下执行
- print('你好啊,我叫赛利亚')
- # 利用事件模拟红绿灯
- import time
- import random
- from multiprocessing import Process, Event
- def traffic_light(e):
- while 1:
- print('红灯啦')
- time.sleep(5) # 此时事件状态初始为False
- e.set() # 绿灯亮, 事件状态更改为True
- print('绿灯亮了, 可以走了~')
- time.sleep(3)
- e.clear() # 将事件状态初始化成False
- def car(i, e):
- if not e.is_set(): # 如果事件是False执行此条件下代码
- print('汽车%s号正在等待红灯...' % i)
- e.wait() # 等待事件状态变成True后继续往下执行
- print('汽车%s号开走了' % i)
- else: # 当e.is_set()变成True时执行此条件下代码
- print('汽车%s号畅通无阻地开了过去' % i) # 此时是绿灯, 无需等待, 直接畅行
- if __name__ == '__main__':
- e = Event() # 创建一个事件对象
- tra_p = Process(target=traffic_light, args=(e,)) # 创建红绿灯进程
- tra_p.start()
- # 为了不断测试红绿灯效果, 10辆车不够用, 就循环创建序号从1-10的车辆
- while 1:
- for i in range(1, 11): # 车辆编号1-10
- time.sleep(random.randrange(1, 5)) # 每隔随机1-4秒
- car_p = Process(target=car, args=(i, e)) # 创建一辆车
- car_p.start() # 开启车辆进程
进程间通信 IPC:
1. 队列 (重点)
Queue
- Queue([maxsize]) 创建共享的进程队列。
- 参数 :maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。
- 底层队列使用管道和锁实现。
q.empty() 判断队列是否是空
如果调用此方法时 q为空,返回True。如果其他进程或线程正在往队列中添加项目,结果是不可靠的。也就是说,在返回和使用结果之间,队列中可能已经加入新的项目。
q.full() 判断队列是否已满
由于线程的存在,结果也可能是不可靠的(参考q.empty()方法)。。
q.qsize() 获取队列的大小. 结果也不可靠.
queue的其他方法(了解)
- q.close()
- 关闭队列,防止队列中加入更多数据。调用此方法时,后台线程将继续写入那些已入队列但尚未写入的数据,但将在此方法完成时马上关闭。如果q被垃圾收集,将自动调用此方法。关闭队列不会在队列使用者中生成任何类型的数据结束信号或异常。例如,如果某个使用者正被阻塞在get()操作上,关闭生产者中的队列不会导致get()方法返回错误。
- q.cancel_join_thread()
- 不会再进程退出时自动连接后台线程。这可以防止join_thread()方法阻塞。
- q.join_thread()
- 连接队列的后台线程。此方法用于在调用q.close()方法后,等待所有队列项被消耗。默认情况下,此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread()方法可以禁止这种行为。
- from multiprocessing import Process, Queue
- # 专门设置线程间进行通信的
- # 先进先出
- q = Queue(3) # 队列长度3, 可以放3条 数据
- q.put(1)
- q.put(2)
- q.put(3)
- # q.put(4) # 超出队列长度,阻塞住啦 此处可以使用 q.put_nowait(4),此种情况下, 队列满了不会阻塞, 但是会报错, 可以抛异常和异常处理.
- print('队列是否满了>>>', q.full()) # 判断队列是否满了
- print(q.get()) # 取出1
- print(q.get()) # 取出2
- print(q.get()) # 取出3
- print('队列是否为空:', q.empty())
- # print(q.get()) # 此时队列为空, 阻塞住
- q.put(4) ####################################
- print('又放了个数据进来')
- print('队列是空吗?', q.empty())
- # while 1: # 加了循环就可以等着队列有新增的时候拿东西
- try:
- print(q.get(False)) # get的默认参数是True,如果队列空了取不到就阻塞住,改为False就不会阻塞,但是会报错
- # q.get_nowait() # 和get(False)一个效果
- except: # 异常处理
- print('队列目前是空的')
- # 队列在多线程中的应用
- import time
- from multiprocessing import Process, Queue
- def girl(q):
- print('来自男孩的消息', q.get())
- print('学校广播', q.get())
- def boy(q):
- q.put('你好啊,女孩')
- if __name__ == '__main__':
- q = Queue(5)
- b = Process(target=boy, args=(q,))
- g = Process(target=girl, args=(q,))
- b.start()
- g.start()
- time.sleep(0.1)
- q.put('好好学习')
2. 生产者消费者模型
为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继 续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力,这个阻塞队列就是用来给生产者和消费者解耦的. 并且我可以根据生产速度和消费速度来均衡一下多少个生产者可以为多少个消费者提供足够的服务,就可以开多进程等等,而这些进程都是到阻塞队列或者说是缓冲区中去获取或者添加数据。
生产者消费者中间有个柜台(缓冲区), 彼此都不用等待对方(阻塞), 等待期间可以处理自己的事情. 解耦缓冲, 解决双方效率差异问题.
- # 版本1 有bug
import time- from multiprocessing import Process, Queue
- def producer(q):
- for i in range(1, 11):
- time.sleep(0.5)
- print('生产了包子%s号' % i)
- q.put(i)
- def consumer(q):
- while 1:
- time.sleep(0.8)
- try:
- s = q.get(False) # 不加False的话等队列中的包子都吃完了会阻塞住
- print('顾客吃了包子%s号' % s)
- except:
- print('包子店关门了')
- break
- # 注意,如果此时顾客吃的比店铺生产的快, 就会先触发break, 所以此版本有缺陷
- if __name__ == '__main__':
- # 通过队列来摸底缓冲区, 设置缓冲区大小20
- q = Queue(20)
- pro_p = Process(target=producer, args=(q,))
- pro_p.start()
- con_p = Process(target=consumer, args=(q,))
- con_p.start()
- # 版本2子进程发送结束信号 消费者吃得快
- import time
- from multiprocessing import Process, Queue
- def producer(q):
- for i in range(1, 11):
- time.sleep(1)
- print('生产了包子%s号' % i)
- q.put(i)
- q.put(None) # 给消费者发送一个结束信号
- def consumer(q):
- while 1:
- s = q.get()
- if s == None:
- print('包子店关门了')
- break
- else:
- time.sleep(0.6)
- print('顾客吃了包子%s号' % s)
- # 注意,如果此时顾客吃的比店铺生产的快, 就会先触发break, 所以此版本有缺陷
- if __name__ == '__main__':
- # 通过队列来摸底缓冲区, 设置缓冲区大小20
- q = Queue(20)
- pro_p = Process(target=producer, args=(q,))
- pro_p.start()
- con_p = Process(target=consumer, args=(q,))
- con_p.start()
- # 版本3 主进程发送结束信号 消费者吃得快
- import time
- from multiprocessing import Process, Queue
- def producer(q):
- for i in range(1, 11):
- time.sleep(1)
- print('生产了包子%s号' % i)
- q.put(i)
- def consumer(q):
- while 1:
- s = q.get()
- if s == None:
- print('包子店关门了')
- break
- else:
- time.sleep(0.6)
- print('顾客吃了包子%s号' % s)
- # 注意,如果此时顾客吃的比店铺生产的快, 就会先触发break, 所以此版本有缺陷
- if __name__ == '__main__':
- # 通过队列来摸底缓冲区, 设置缓冲区大小20
- q = Queue(20)
- pro_p = Process(target=producer, args=(q,))
- pro_p.start()
- con_p = Process(target=consumer, args=(q,))
- con_p.start()
- pro_p.join()
- q.put(None)
版本3 主进程发送结束信号
3. JoinableQueue 生产者消费者模型
- # 通过joinableQueue结束消费者子进程
- import time
- from multiprocessing import Process, Queue, JoinableQueue
- def prodecer(q):
- for i in range(1, 11):
- time.sleep(1)
- print('生产了包子%s号' % i)
- q.put(i)
- # 记录了往队列中放了多少个和取出了多少个(收到了多少个task_done))
- q.join()
- # 等待接收全部task_done信号(共10个), 接收后才继续
- print('客人都走了,关门中...')
- def consumer(q):
- while 1:
- time.sleep(2)
- s = q.get()
- print('消费者吃了包子%s号' % s)
- q.task_done() # 每从队列中获取一个包子就发送一个task_done信号给生产者
- if __name__ == '__main__':
- q = JoinableQueue(20)
- pro_p = Process(target=prodecer, args=(q,))
- pro_p.start()
- con_p = Process(target=consumer, args=(q,))
- con_p.daemon = True # 把消费者子进程设置为守护进程,当主进程结束, 消费者子进程也结束 如果不设置守护进程, 消费者进程就会在while循环中的q.get()阻塞住,队列中已经没有包子了
- con_p.start()
- pro_p.join() # 等待生产者子进程结束, 主进程才结束
- print('主进程结束')
- #JoinableQueue([maxsize]):这就像是一个Queue对象,但队列允许项目的使用者通知生成者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。
- #参数介绍:
- maxsize是队列中允许最大项数,省略则无大小限制。
- #方法介绍:
- JoinableQueue的实例p除了与Queue对象相同的方法之外还具有:
- q.task_done():使用者使用此方法发出信号,表示q.get()的返回项目已经被处理。如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常
- q.join():生产者调用此方法进行阻塞,直到队列中所有的项目均被处理。阻塞将持续到队列中的每个项目均调用q.task_done()方法为止,也就是队列中的数据全部被get拿走了。
- import time
- from multiprocessing import Process, JoinableQueue
- def producer(food, q):
- for i in range(1, 21):
- food_info = '%s%s号' % (food, i)
- time.sleep(8)
- q.put(food_info)
- print('生产了%s%s号' % (food, i))
- q.join()
- print('%s都卖完了, %s店关门了' % (food, food))
- q.join()
- def consumer(i, q):
- while 1:
- time.sleep(5)
- print('客户%s吃了%s' % (i, q.get()))
- q.task_done()
- if __name__ == '__main__':
- q = JoinableQueue()
- pro_p1 = Process(target=producer, args=('包子', q))
- pro_p1.start()
- pro_p2 = Process(target=producer, args=('炸鱼薯条', q))
- pro_p2.start()
- pro_p3 = Process(target=producer, args=('大碗茶', q))
- pro_p3.start()
- for i in range(1, 31):
- con_p = Process(target=consumer, args=(i, q))
- con_p.daemon = True
- con_p.start()
- pro_p1.join()
- pro_p2.join()
- pro_p3.join()
- print('店铺都关门了, 今天吃不到夜宵了')
多个生产者消费者模型一
- import time
- import random
- from multiprocessing import Process, JoinableQueue
- def producer(num, q):
- for i in range(1, 11):
- time.sleep(random.randrange(1, 3))
- q.put(i)
- print('生产者%s号生产了包子%s' % (num, i))
- q.join()
- print('包子店%s打烊了...明天再来吧' % num)
- def consumer(num, q):
- while 1:
- time.sleep(random.randrange(2, 6))
- s = q.get()
- print('消费者\033[31m%s\033[0m吃了包子%s' % (num, s))
- q.task_done()
- if __name__ == '__main__':
- q = JoinableQueue()
- c_lst = []
- p_lst = []
- for num in range(1, 4):
- pro_p = Process(target=producer, args=(num, q))
- pro_p.start()
- p_lst.append(pro_p)
- for num in range(1, 11):
- con_p = Process(target=consumer, args=(num, q))
- con_p.daemon = True
- con_p.start()
- c_lst.append(con_p)
- for pro_p in p_lst:
- pro_p.join()
- print('包子店全都关门了, 买不到了')
多个消费者和生产者模型二
Day034--Python--锁, 信号量, 事件, 队列, 生产者消费者模型, joinableQueue的更多相关文章
- python 锁 信号量 事件 队列
什么是python 进程锁? #同步效率低,但是保证了数据安全 重点 很多时候,我们需要在多个进程中同时写一个文件,如果不加锁机制,就会导致写文件错乱 这个时候,我们可以使用multiprocess ...
- #queue队列 #生产者消费者模型
#queue队列 #生产者消费者模型 #queue队列 #有顺序的容器 #程序解耦 #提高运行效率 #class queue.Queue(maxsize=0) #先入先出 #class queue.L ...
- python 全栈开发,Day39(进程同步控制(锁,信号量,事件),进程间通信(队列,生产者消费者模型))
昨日内容回顾 python中启动子进程并发编程并发 :多段程序看起来是同时运行的ftp 网盘不支持并发socketserver 多进程 并发异步 两个进程 分别做不同的事情 创建新进程join :阻塞 ...
- python 并发编程 锁 / 信号量 / 事件 / 队列(进程间通信(IPC)) /生产者消费者模式
(1)锁:进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理. 虽然使用加锁的形式实现了 ...
- python网络编程--进程(方法和通信),锁, 队列,生产者消费者模型
1.进程 正在进行的一个过程或者说一个任务.负责执行任务的是cpu 进程(Process: 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础.在 ...
- 4月25日 python学习总结 互斥锁 IPC通信 和 生产者消费者模型
一.守护进程 import random import time from multiprocessing import Process def task(): print('name: egon') ...
- python2.0_s12_day9之day8遗留知识(queue队列&生产者消费者模型)
4.线程 1.语法 2.join 3.线程锁之Lock\Rlock\信号量 4.将线程变为守护进程 5.Event事件 * 6.queue队列 * 7.生产者消费者模型 4.6 queue队列 que ...
- 守护、互斥锁、IPC和生产者消费者模型
守护进程 主进程创建守护进程 其一:守护进程会在主进程代码执行结束后就终止 其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are ...
- 队列&生产者消费者模型
队列 ipc机制:进程通讯 管道:pipe 基于共享的内存空间 队列:pipe+锁 queue from multiprocessing import Process,Queue ### 案例一 q ...
随机推荐
- EChart.js 笔记一
一直对数据可视化比较感兴趣,当年 Alibaba 年报晚会上的大屏显示可谓是技惊四座,够震撼,将数据之美展现得淋漓尽致. 国内的前端数据可视化插件中,echart.js 算是热度很高的,也容易上手,算 ...
- 3.ansible-iventory的写法和基本变量
ansible的配置文件一点要多考虑,有些设定比如ssh端口啊用户啊线程啊都尽量在里面调节好iventory的话/etc/ansible/hosts 里面可以使用正则匹配ansible从invento ...
- 【python练习题】程序8
#题目:输出 9*9 乘法口诀表. for i in range(1,10): k = '' for j in range(1,i+1): k += '%s * %s = %s '%(i,j,i*j) ...
- 转载:实现MATLAB2016a和M文件关联
转载自http://blog.csdn.net/qq_22186119 新安装MATLAB2016a之后,发现MATLAB没有和m文件关联 每次打开m文件后都会重新打开一次MATLAB主程序 后来发现 ...
- Nginx 用分片提示缓存效率
L:107 slice 模块 Syntax:slice size;Default: slice 0; Context:http, server, location 功能:通过range协议将大文件分解 ...
- 【C/C++】实现龙贝格算法
1. 复化梯形法公式以及递推化 复化梯形法是一种有效改善求积公式精度的方法.将[a,b]区间n等分,步长h = (b-a)/n,分点xk = a + kh.复化求积公式就是将这n等分的每一个小区间进行 ...
- 如何简单实现接口自动化测试(基于 python) 原博主地址https://blog.csdn.net/gitchat/article/details/77849725
如何简单实现接口自动化测试(基于 python) 2017年09月05日 11:52:25 阅读数:9904 GitChat 作者:饿了么技术社区 原文:如何简单实现接口自动化测试(基于 python ...
- 【XSY2715】回文串 树链剖分 回文自动机
题目描述 有一个字符串\(s\),长度为\(n\).有\(m\)个操作: \(addl ~c\):在\(s\)左边加上一个字符\(c\) \(addr~c\):在\(s\)右边加上一个字符 \(tra ...
- luogu P3128 [USACO15DEC]最大流Max Flow (树上差分)
题目描述 Farmer John has installed a new system of N-1N−1 pipes to transport milk between the NN stalls ...
- XAMPP Access forbidden! Error 403,You don't have permission to access the requested directory
xampp 无论在window 还是在 Mac 如出现以下错误的:通常的解决方式: 具体配置教程可以任意查相关资料既可,(配置子站子大致流程如:开启httpd.conf的inc...httpd-vho ...