进程 线程(二)

 

进程

启动多个进程,进程之间通过操作系统调用,操作系统又有一个时间片的概念,这样多CPU的话就可以调用多个进程。

线程

启动多个线程,真正被CPU执行的最小单位是线程,在Cpython中由于GIL锁的概念,同一时刻只能有一个线程工作。再其它语言中允许用一时间调用多个线程执行。不会影响高IO的操作。

弊端:开启一个线程,创建一个线程 需要创建寄存器,堆栈。
 

协程

本质就是一个线程,在多个任务之间来回调用,不需要创建寄存器,堆栈。

 

进程:
    进程是一个正在运行的程序,程序不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序称之为进程。
进程的提出是在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行,大大提高了CPU的利用率。进程就是为了在CPU上实现多道编程而提出的。

但是进程也有很多缺点例如:
    - 进程只能在同一时间干一件事,如果同时干两件事或多件事,进程就无能为力了。
    - 进程在执行的过程中如果出现阻塞,例如等待输入,整个进程会被挂起。
    - 进程开销大,创建,撤销与切换存在较大的时空开销。

后来出现了线程,线程是CPU调度的最小单位,每个进程中至少有一个线程。

线程比进程的优势:
    - 地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程共享。
    - 通信: 进程间通信IPC,线程间可以直接读写进程的资源。
    - 调度和切换:线程上下文切换要比进程上下文切换要快得多。

全局解释器锁GIL
    - 由于CPython解释器锁的原因,同一时刻只能有一个线程运行,所以Python在CPython解释器中的多线程形同虚设。


递归锁和互斥锁
    递归锁(RLock)是为了解决死锁问题,且在线程中,递归锁可以被acquire多次。
    互斥锁(Lock)

信号量        KTV同一时刻只能有几个人进入到这个房间  同一时间只能有N个线程处理

事件
View Code

 

  

 

 

创建进程两种方式

 

守护进程

进程对象.deamon  值为True的时候,表示新的子进程是一个守护进程,守护进程随着主进程代码色执行结束而结束。在start之前运行

  

 

进程同步控制 

进程锁 

为了数据安全
事例:12306售票一个文件{"ticket":1},有1张票,有10个人过来买票,每个人都看到为1张票,抢票。
#

# 火车票
import json
import time
from multiprocessing import Process
from multiprocessing import Lock

# def show(i):
#     with open('ticket') as f:
#         dic = json.load(f)
#     print('余票: %s'%dic['ticket'])

def buy_ticket(i,lock):
    lock.acquire() #拿钥匙进门
    with open('ticket') as f:
        dic = json.load(f)
        time.sleep(0.1)
    if dic['ticket'] > 0 :
        dic['ticket'] -= 1
        print('\033[32m%s买到票了\033[0m'%i)
    else:
        print('\033[31m%s没买到票\033[0m'%i)
    time.sleep(0.1)
    with open('ticket','w') as f:
        json.dump(dic,f)
    lock.release()      # 还钥匙

if __name__ == '__main__':
    # for i in range(10):
    #     p = Process(target=show,args=(i,))
    #     p.start()
    lock = Lock()
    for i in range(10):
        p = Process(target=buy_ticket, args=(i,lock))
        p.start()
View Code

信号量

事例:KTV20个人想进房间,一个房间同时只能有4个人。
# 多进程中的组件
# ktv
# 4个
# 一套资源  同一时间 只能被n个人访问
# 某一段代码 同一时间 只能被n个进程执行
import time
import random
from multiprocessing import Process
from multiprocessing import Semaphore

def ktv(i,sem):
    sem.acquire()    #获取钥匙
    print('%s走进ktv'%i)
    time.sleep(random.randint(1,5))
    print('%s走出ktv'%i)
    sem.release()   


if __name__ == '__main__' :
    sem = Semaphore(4)
    for i in range(20):
        p = Process(target=ktv,args=(i,sem))
        p.start()
View Code

事件

一个事件被创建之后,默认是阻塞状态。
set和clear 分别用来修改一个时间的状态 True或False
is_set 	   用来查看一个事件的状态
wait	   依据事件的状态决定自己是否阻塞,False阻塞,True不阻塞。如果为True代码执行。
事例: 20辆车等红绿灯
# 红绿灯事件
import time
import random
from multiprocessing import Event,Process
def cars(e,i):
    if not e.is_set():
        print('car%i在等待'%i)
        e.wait()    # 阻塞 直到得到一个 事件状态变成 True 的信号
    print('\033[0;32;40mcar%i通过\033[0m' % i)

def light(e):
    while True:
        if e.is_set():
            e.clear()
            print('\033[31m红灯亮了\033[0m')
        else:
            e.set()
            print('\033[32m绿灯亮了\033[0m')
        time.sleep(2)

if __name__ == '__main__':
    e = Event()
    traffic = Process(target=light,args=(e,))
    traffic.start()
    for i in range(20):
        car = Process(target=cars, args=(e,i))
        car.start()
        time.sleep(random.random())
View Code

进程通信 IPC

队列

两个队列通信
from multiprocessing import Queue,Process
def produce(q):
    q.put('hello')

def consume(q):
    print(q.get())

if __name__ == '__main__':
    q = Queue()
    p = Process(target=produce,args=(q,))
    p.start()
    c = Process(target=consume, args=(q,))
    c.start()
View Code

 

  

管道

 

进程池

#为什么会有进程池的概念?
	每开启一个进程,就会创建属于这个进程的内存空间(寄存器 堆栈 文件),所以开启多个进程会耗时。
	进程过多,操作系统需要调度,当切换时,需要当前进程保存状态,所以不会无休止的任由进程的创建。
	进程池刚开始会在进程池中创建4个进程,无论多少个任务来时都由这几个进程处理。  

进程池基本事例

import time
from multiprocessing import Pool,Process

def func(n):
    for i in range(10):
        print(n+1)

if __name__ == '__main__':

    start = time.time()
    pool = Pool(5)               # 5个进程
    pool.map(func,range(100))    # 100个任务
    t1 = time.time() - start



    start1 = time.time()
    p_lst = []
    for i in range(100):
        p = Process(target=func,args=(i,))
        p_lst.append(p)
        p.start()
    for p in p_lst :p.join()
    t2 = time.time() - start1
    print(t1,t2)




------------结果


0.03202390670776367 0.18317079544067383
开启100个任务,分别用多进程和进程池执行,发现进程池所花费的时间少

异步开启子进程 

import os
import time
from multiprocessing import Pool
def func(n):
    print('start func%s'%n,os.getpid())
    time.sleep(1)
    print('end func%s' % n,os.getpid())

if __name__ == '__main__':
    p = Pool(5)
    for i in range(10):
        p.apply_async(func,args=(i,))

--------结果:
没有任何输出


import os
import time
from multiprocessing import Pool
def func(n):
    print('start func%s'%n,os.getpid())
    time.sleep(1)
    print('end func%s' % n,os.getpid())

if __name__ == '__main__':
    p = Pool(5)
    for i in range(10):
        p.apply_async(func,args=(i,))
    p.close()          # 结束进程池接收任务
    p.join()           # 感知进程池中的任务执行结束


--------结果:
子进程正常输出,主进程等待子进程结束而结束。
p.apply_async 异步执行子进程

基于进程池实现socketserver 

#服务端

import socket
from multiprocessing import Pool

def func(conn):
    conn.send(b'hello')
    print(conn.recv(1024).decode('utf-8'))
    conn.close()

if __name__ == '__main__':
    p = Pool(5)
    sk = socket.socket()
    sk.bind(('127.0.0.1',8080))
    sk.listen()
    while True:
        conn, addr = sk.accept()
        p.apply_async(func,args=(conn,))
    sk.close()






#客户端
import socket

sk = socket.socket()
sk.connect(('127.0.0.1',8080))

ret = sk.recv(1024).decode('utf-8')
print(ret)
msg = input('>>>').encode('utf-8')
sk.send(msg)
sk.close()
进程池实现socket效果,支持5个进程同时连接通信

 

线程

socket聊天并发效果

#服务端

import socket
from threading import Thread

def chat(conn):
    conn.send(b'hello')
    msg = conn.recv(1024).decode('utf-8')
    print(msg)
    conn.close()

sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
while True:
    conn,addr = sk.accept()
    Thread(target=chat,args = (conn,)).start()
sk.close()


#客户端

import socket

sk = socket.socket()
sk.connect(('127.0.0.1',8080))

msg = sk.recv(1024)
print(msg)
inp = input('>>> ').encode('utf-8')
sk.send(inp)
sk.close()
基于多线程实现secketserver

 

递归锁和互斥锁

递归锁(RLock)是为了解决死锁问题,且在线程中,递归锁可以被acquire多次。
互斥锁(Lock)

信号量

KTV同一时刻只能有几个人进入到这个房间 同一时间只能有N个线程处理

Event 事件 

# 事件被创建的时候
# False状态
    # wait() 阻塞
# True状态
    # wait() 非阻塞
# clear 设置状态为False
# set  设置状态为True


#  起两个线程
#  第一个线程 : 连接数据库
        # 等待一个信号 告诉我我们之间的网络是通的
        # 连接数据库
#  第二个线程 : 检测与数据库之间的网络是否连通
        # time.sleep(0,2) 2
        # 将事件的状态设置为True
import time
import random
from threading import Thread,Event

def connect_db(e):
    count = 0
    while count < 3:
        e.wait(0.5)       # 状态为False的时候,我只等待1s就结束
        if e.is_set() == True:
            print('连接数据库')
            break
        else:
            count += 1
            print('第%s次连接失败'%count)
    else:
        raise TimeoutError('数据库连接超时')


def check_web(e):
    time.sleep(random.randint(0,3))
    e.set()

e = Event()
t1 = Thread(target=connect_db,args=(e,))
t2 = Thread(target=check_web,args=(e,))
t1.start()
t2.start()
事件 连接数据库,每次等1秒 有3次机会

 

协程 

在一个线程中实现并发效果
能够规避一些任务中IO操作
在任务的执行过程中,检测到IO就切换到其他任务

  

基于协程爬虫 

from gevent import monkey;monkey.patch_all()
import gevent
from urllib.request import urlopen    # 内置的模块
def get_url(url):
    response = urlopen(url)
    content = response.read().decode('utf-8')
    return len(content)

g1 = gevent.spawn(get_url,'http://www.baidu.com')
g2 = gevent.spawn(get_url,'http://www.sogou.com')
g3 = gevent.spawn(get_url,'http://www.taobao.com')
g4 = gevent.spawn(get_url,'http://www.hao123.com')
g5 = gevent.spawn(get_url,'http://www.cnblogs.com')
gevent.joinall([g1,g2,g3,g4,g5])
print(g1.value)
print(g2.value)
print(g3.value)
print(g4.value)
print(g5.value)
协程爬虫事例

 

协程实现socketserver 

#服务端

from gevent import monkey;monkey.patch_all()
import socket
import gevent
def talk(conn):
    conn.send(b'hello')
    print(conn.recv(1024).decode('utf-8'))
    conn.close()

sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
while True:
    conn,addr = sk.accept()
    gevent.spawn(talk,conn)
sk.close()




#客户端

import socket
sk = socket.socket()
sk.connect(('127.0.0.1',8080))
print(sk.recv(1024))
msg = input('>>>').encode('utf-8')
sk.send(msg)
sk.close()
View Code

 

  

posted @ 2018-05-09 10:18  golangav  阅读(307)  评论(0编辑  收藏  举报