网络编程之:线程thread与进程process

1 线程thread与进程process

进程process

  • 一个程序的执行实例称为进程;即:程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,执行的程序就称之为进程。

  • 程序和进程的区别:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。

  • 进程的缺点:

    进程只能在一个时间干一件事。
    进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。

线程thread

  • 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
  • I/O操作不占用CPU,计算占用CPU,python多线程不适合CPU密集操作型的任务,适合I/O操作密集型的任务。

进程与线程的区别

  1. 线程共享进行创建的内存地址空间;而每个进程各自有独立的内存地址空间,进程之间无法共享内存地址空间。
  2. 线程直接访问创建它的进程的数据片段;进程只复制父进程的数据片段。
  3. 线程可以直接在同个进程与其他线程进行互访;进程与兄弟进程互访必须通过进程间的互访。
  4. 新的线程比较容易创建;新的进程需要复制父进程。
  5. 线程对同一进程中的线程执行具有相当大的控制;进程只能控制其子进程。
  6. 更改主线程会影响同进程下其他线程的行为;更改父进行不会影响其子进程。

2 Python GIL(Global Interpreter Lock)

  • 在CPython中,全局解释器锁GIL作用是阻止本地多个线程当前执行python 脚本,即即使是多线程,CPython在处理的过程中仍然是在同一时间只允许一个线程运行。

3 线程

3.1 Join & Daemon

  • join等待所有线程运算结束时,结束。
import threading
import time


def run(n):
    print('task', n)
    time.sleep(2)
    print("%s done" % n, threading.current_thread())


start_time = time.time()
t_objs = []  # 存线程实例
for i in range(50):
    t = threading.Thread(target=run, args=('task-%s' % i, ))  # 生成一个线程实例
    t.start()  # 启动线程
    t_objs.append(t)  # 为了不阻塞后面线程的启动,不在这里join,先放入此列表中

for t in t_objs:  # 循环线程实例列表,等待所有线程执行完毕
    t.join()

print('all threads has done', threading.current_thread(), threading.activeCount())  # threading.current_thread()打印当前线程号,threading.activeCount()打印当前活动线程数。
print('cost:',time.time()-start_time)


# 运行结果
all threads has done <_MainThread(MainThread, started 1520)> 51
cost: 0.01614093780517578
task-2 donetask-3 done task-1 donetask-0 done <Thread(Thread-4, started 13664)>
 <Thread(Thread-3, started 6028)>

  • Daemon只要主线程运算结束,全部结束。
import threading
import time


def run(n):
    print('task', n)
    time.sleep(2)
    print("%s done" % n, threading.current_thread())


start_time = time.time()
t_objs = []  # 存线程实例
for i in range(50):
    t = threading.Thread(target=run, args=('task-%s' % i, ))
    t.setDaemon(True)  # #将主线程设置为Daemon线程,它做为程序主线程的守护线程,当主线程退出时,由主线程启动的其它子线程会同时退出,不管是否执行完任务
    t.start()
    t_objs.append(t)  # 为了不阻塞后面线程的启动,不在这里join,先放入此列表中

print('all threads has done', threading.current_thread(), threading.activeCount())
print('cost:',time.time()-start_time)

# 运行结果
task task-47
task task-48
taskall threads has done <_MainThread(MainThread, started 12976)> 51
cost: 0.013181686401367188

3.2 线程锁之Lock\Rlock\信号量

3.2.1 线程锁(互斥锁/Mutex lock)

  • Lock: 这里的lock是用户级的lock,跟那个GIL没关系 ,主要防止如果2个线程同时要修改同一份数据.

  • 没有加锁之前

in python 2.7.1
import threading
import time


def run(n):
    global num  # 在每个线程中都获取这个全局变量
    time.sleep(1)
    num += 1


num = 0
t_objs = []  # 存线程实例
for i in range(500):
    t = threading.Thread(target=run, args=('task-%s' % i, ))
    t.start()
    t_objs.append(t)  # 为了不阻塞后面线程的启动,不在这里join,先放入此列表中

for t in t_objs:  # 循环线程实例列表,等待所有线程执行完毕
    t.join()

print('all threads has done', threading.current_thread(), threading.activeCount())
print('num', num)

# 输出结果
('all threads has done', <_MainThread(MainThread, started 3976)>, 1)
('num', 464)

正常来说,num应该是500,但在Python2.7上运行多次,会发现每次结果都不相同,且总小于等于500* 解析:
假设你有A,B两个线程,此时都要对num 进行加1操作,由于2个线程是并发同时运行的,所以2个线程很有可能同时拿走了num=100这个初始变量交给cpu去运算,当A线程去处完的结果是101,但此时B线程运算完的结果也是101,两个线程同时CPU运算的结果再赋值给num变量后,结果就都是101* 解决方式:
每个线程在要修改公共数据时,给这个数据加一把锁,其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。 
*注:不要在3.x上运行,不知为什么,3.x上的结果总是正确的,可能是自动加了锁
  • 加锁后
import threading
import time


def run(n):
    global num  # 在每个线程中都获取这个全局变量
    time.sleep(1)
    lock.acquire()  # 修改前对数据加锁
    num += 1
    lock.release()  # 修改后释放


num = 0
t_objs = []  # 存线程实例
lock = threading.Lock()  # 生成全局锁
for i in range(500):
    t = threading.Thread(target=run, args=('task-%s' % i, ))
    t.start()
    t_objs.append(t)  # 为了不阻塞后面线程的启动,不在这里join,先放入此列表中

for t in t_objs:  # 循环线程实例列表,等待所有线程执行完毕
    t.join()

print('all threads has done', threading.current_thread(), threading.activeCount())
print('num', num)

3.2.2 RLock(递归锁)

# 防止程序调用错误的锁,需要使用RLock
import threading, time


def run1():
    print("grab the first part data")
    lock.acquire()
    global num
    num += 1
    lock.release()
    return num


def run2():
    print("grab the second part data")
    lock.acquire()
    global num2
    num2 += 1
    lock.release()
    return num2


def run3():
    lock.acquire()
    res = run1()
    print('--------between run1 and run2-----')
    res2 = run2()
    lock.release()
    print(res, res2)

num, num2 = 0, 0
lock = threading.RLock()
for i in range(10):
    t = threading.Thread(target=run3)
    t.start()


while threading.active_count() != 1:
    print(threading.active_count())
else:
    print('----all threads done---')
    print(num, num2)

3.2.3 Semaphore信号量/线程池

  • Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。
  • 使用方式:线程池
import threading, time


def run(n):
    semaphore.acquire()
    time.sleep(1)
    print("run the thread: %s\n" % n)
    semaphore.release()


if __name__ == '__main__':

    semaphore = threading.BoundedSemaphore(5)  # 最多允许5个线程同时运行
    for i in range(20):
        t = threading.Thread(target=run, args=(i,))
        t.start()

while threading.active_count() != 1:
    pass  # print threading.active_count()
else:
    print('----all threads done---')

3.3 Timer时间/事件Event

3.3.1 Timer时间

  • Timer跟随线程启动,使用start()方法启动,使用cancel()方法停止。
  • 作用是等待设定的时间间隔执行命令。
from threading import Timer

def hello():
    print("hello, world")


t = Timer(30.0, hello)
t.start()  # after 30 seconds, "hello, world" will be printed

3.3.2 事件Event

  • Event(事件):事件处理的机制:全局定义了一个内置标志Flag,如果Flag值为 False,那么当程序执行 event.wait方法时就会阻塞,如果Flag值为True,那么event.wait 方法时便不再阻塞。
  • Event其实就是一个简化版的 Condition。Event没有锁,无法使线程进入同步阻塞状态。
  • set(): 将标志设为True,并通知所有处于等待阻塞状态的线程恢复运行状态。
  • clear(): 将标志设为False。
  • wait(timeout): 如果标志为True将立即返回,否则阻塞线程至等待阻塞状态,等待其他线程调用set()。
  • isSet(): 获取内置标志状态,返回True或False。
# 通过Event来实现多个线程控制,一个红绿灯的例子,即起动一个线程做交通指挥灯,生成几个线程做车辆,车辆行驶按红灯停,绿灯行的规则。
import threading,time


event = threading.Event()

def lighter():
    count = 0
    event.set()  # 先设置初始标志位,默认为绿灯
    while True:
        if 4 < count < 10:
            event.clear()  # 标志位清空,wait卡住
            print('\033[41;1m red light is on ...\033[0m')
        elif count > 10:
            event.set()  # 重新设备标志位,wait放行
            count = 0
        else:
            print('\033[42;1m green light is on ...\033[0m')
        time.sleep(1)
        count += 1

def car(name):
    while True:
        if event.isSet():  # 标志位设置为绿灯
            print('[%s] running...' % name)
            time.sleep(1)
        else:
            print("[%s] sees red light , waitting" % name)
            event.wait()
            print("\033[034;1m[%s] green light is on, start going... \033[0m" % name)

lighter1 = threading.Thread(target=lighter,)
lighter1.start()

car1 = threading.Thread(target=car, args=('mycar',))
car1.start()

3.4 队列Queue

队列是确保消息被多个线程准确进行交换的一个特殊的线程程序

3.4.1 先进先出(FIFO)队列

class asyncio.Queue(maxsize=0, *, loop=None)

maxsize  # 队列中可存放的元素数量。如果 maxsize 小于等于零,则队列尺寸是无限的。如果是大于 0 的整数,则当队列达到 maxsize 时, await put() 将阻塞至某个元素被 get() 取出。

empty()  # 如果队列为空返回 True ,否则返回 False 。

full()  # 如果有 maxsize 个条目在队列中,则返回 True 。如果队列用 maxsize=0 (默认)初始化,则 full() 永远不会返回 True 。

coroutine get()  # 从队列中删除并返回一个元素。如果队列为空,则等待,直到队列中有元素。

get_nowait()  # 立即返回一个队列中的元素,如果队列内有值,否则引发异常 QueueEmpty 。

coroutine join()  # 阻塞至队列中所有的元素都被接收和处理完毕。当条目添加到队列的时候,未完成任务的计数就会增加。每当消费协程调用 task_done() 表示这个条目已经被回收,该条目所有工作已经完成,未完成计数就会减少。当未完成计数降到零的时候, join() 阻塞被解除。

coroutine put(item)  # 添加一个元素进队列。如果队列满了,在添加元素之前,会一直等待空闲插槽可用。

put_nowait(item)  # 不阻塞的放一个元素入队列。如果没有立即可用的空闲槽,引发 QueueFull 异常。

qsize()  # 返回队列用的元素数量。

task_done()  # 表明前面排队的任务已经完成,即get出来的元素相关操作已经完成。由队列使用者控制。每个 get() 用于获取一个任务,任务最后调用 task_done() 告诉队列,这个任务已经完成。
# 如果 join() 当前正在阻塞,在所有条目都被处理后,将解除阻塞(意味着每个 put() 进队列的条目的 task_done() 都被收到)。
# 如果被调用的次数多于放入队列中的项目数量,将引发 ValueError 。

3.4.2 优先级队列

class asyncio.PriorityQueue  # Queue 的变体;按优先级顺序取出条目 (最小的先取出)。条目通常是 (priority_number, data) 形式的元组。

3.4.3 后进先出队列

class asyncio.LifoQueue  # Queue 的变体,先取出最近添加的条目(后进,先出)。

3.4.4 异常

exception asyncio.QueueEmpty  # 当队列为空的时候,调用 get_nowait() 方法而引发这个异常。
exception asyncio.QueueFull  # 当队列中条目数量已经达到它的 maxsize 的时候,调用 put_nowait() 方法而引发的异常。

3.4.5 生产消费者模型

通过多线程实现

import queue
import threading
import time

q = queue.Queue(maxsize=10)

def producer(name):
    count = 1
    while True:
        q.put('生产%s号包子' % count)
        print('%s生产出%s号包子' % (name, count))
        time.sleep(0.1)
        count += 1

def consumer(name):
    while True:
        print('%s 得到%s号' % (name, q.get()))
        time.sleep(2)

producer1 = threading.Thread(target=producer, args=('carey',))
producer1.start()

consumer1 = threading.Thread(target=consumer, args=('consumer1',))
consumer1.start()

consumer2 = threading.Thread(target=consumer, args=('consumer2',))
consumer2.start()

4 进程

4.1 进程间通信方式

  • 注:进程间通信应该尽量避免使用共享数据的方式

  • **消息队列MessageQueue:**消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

  • **管道pipe:**管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

  • **命名管道FIFO:**有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

  • **共享存储SharedMemory:**共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。

4.1.1 消息队列MessageQueue

from multiprocessing import Process, Queue
import queue

def f(qq):
    qq.put('hello world')


if __name__ == '__main__':
    q = Queue()
    p = Process(target=f, args=(q, ))
    p.start()
    print(q.get())
    p.join()

4.1.2 管道pipe

from multiprocessing import Process, Pipe


def f(conn):
    conn.send('from child')  #主进程使用conn口发送
    print('child_recv: ', conn.recv())  #主进程使用conn口接收
    conn.close()


if __name__ == '__main__':
    parent_conn, child_conn = Pipe()  # 开辟两个口,都是能进能出,如果'()'中是参数为False,则为单向通信。
    p = Process(target=f, args=(child_conn, ))  # 子进程使用sock口,调用f函数。
    p.start()
    print('father recv:', parent_conn.recv())  #主进程使用parent_conn口接收
    parent_conn.send('form father') #主进程使用parent_conn口发送
    p.join()

4.1.3 Manager

  • Manager()返回的manager对象控制了一个server进程,此进程包含的python对象可以被其他的进程通过proxies来访问。从而达到多进程间数据通信且安全。
  • Manager()支持类型: dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array.
from multiprocessing import Process, Manager
import os

def f(d, l):
    d[os.getpid()] = os.getpid()
    l.append(os.getpid())

    print(l)

if __name__ == '__main__':
    with Manager() as manager:
        d = manager.dict()  # 生成一个字典在多个进程间共享和传递

        l = manager.list(range(5))  # 生成一个列表在多个进程间共享和传递

        p_list = []
        for i in range(5):
            p = Process(target=f, args=(d, l))
            p.start()
            p_list.append(p)
        for res in p_list:  # 等待结果
            res.join()

        print('dict: ', d)
        print('list: ', l)

4.2 进程同步

1、基本概念

多个进程可以协同工作来完成一项任务,通常需要共享数据。所以在多进程之间保持数据的一致性就很重要,需要共享数据协同的进程必须以适当的策略来读写数据。同步原语和线程的库类似。

  • Lock:一个Lock对象有两个方法acquire和release来控制共享数据的读写权限。
  • Event:一个进程发事件的信号,另一个进程等待事件的信号。Event对象有两个方法set和clear来管理自己内部的变量。
  • Condition:此对象用来同步部分工作流程,在并行的进程中,有两个基本的方法,wait()用来等待进程,notify_all用来通知所有等待此条件的进程。
  • Semaphore:用来共享资源,比如:支持固定数据的共享连接。
  • RLock:递归锁对象,其用途和方法同Threading模块一样。
  • Barrier:将程序分成几个阶段,适用于有些进程必须在某些特性进程之后执行,处于Barrier之后的代码不能同处于Barrier之前的代码并行。
from multiprocessing import Process, Lock


def f(l, i):
    l.acquire()
    print('hello world', i)
    l.release()


if __name__ == '__main__':
    lock = Lock()

    for num in range(1000):
        Process(target=f, args=(lock, num)).start()

# 输出结果
'''未加锁时状态:
hello world 284
hello worldhello world  283289

hello world 282

加锁时状态
hello world 279
hello world 282
hello world 281
hello world 287
hello world 285
hello world 283
hello world 284
hello world 288
hello world 286
hello world 289'''

4.2 进程池

  • **apply_async(func[, args[, kwds[, callback]]]) : **它是非阻塞,并行打印
  • **apply(func[, args[, kwds]]) : **是阻塞的,串行打印
  • **close() : **关闭pool,使其不在接受新的任务。
  • **terminate() : **结束工作进程,不再处理未完成的任务。
  • **join() : **主进程阻塞,等待子进程的退出, join方法要在close或terminate之后使用。
from multiprocessing import Process, Pool, freeze_support
import os
import time


def Foo(i):
    time.sleep(2)
    return i + 100


def Bar(arg):
    print('-->exec done:', arg)
    print('in bar', os.getpid())

if __name__ == '__main__':
    freeze_support()
    pool = Pool(3)  #  pool = Pool(processes=5)
    print('in process', os.getpid())

    for i in range(10):
        pool.apply_async(func=Foo, args=(i,), callback=Bar)  # 并行,callback回调:进程池.py是Foo执行完成后,调用callback
        # pool.apply(func=Foo, args=(i,))  # 串行

    print('end')
    pool.close()  # 一定要先关闭进程池再join,否则报错:ValueError: Pool is still running
    pool.join()  # 进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。
posted @ 2020-11-07 06:20  f_carey  阅读(12)  评论(0编辑  收藏  举报  来源