线程、进程、协程 - Go语言中文社区

线程、进程、协程


1. 线程和进程对比

使用线程的方式不能很好的使用多核cpu的能力,始终是单核工作。
使用进程可以利用多核CPU的优势。

1.1 线程

写一个线程池:

import threading
import random

my_sum = []


def compute():
    my_sum.append(sum([random.randint(1, 100) for _ in range(1000000)]))


workers = [threading.Thread(target=compute) for _ in range(8)]

for worker in workers:
    worker.start()
    worker.join()  # 等待每一个线程执行完成
print("Results: %s" % my_sum)
# 命令行执行time python my_thread.py 可以查看当前程序执行时间。
Results: [50506721, 50489343, 50504450, 50496305, 50482675, 50486201, 50500850, 50487435]

real    0m10.992s  真实时间
user    0m10.988s 开启的线程
sys     0m0.113s  主线程

1.2 进程

写一个进程池:

import multiprocessing
import random


def compute(n):
    return sum(
        [random.randint(1, 100) for i in range(1000000)])


pool = multiprocessing.Pool(processes=8)
print(pool.map(compute, range(8)))  # map函数必须传入迭代区间
# 命令行执行time python my_process.py 查看程序执行时间
[50479564, 50479179, 50506536, 50516956, 50512819, 50474797, 50476164, 50509185]

real    0m2.783s   真实时间
user    0m18.542s
sys     0m0.061s

PS:通过时间比较,我们看到,进程池的性能远远大于线程池。

1.3 开启普通线程和普通进程

import threading
import time


def foo(name):
    print('执行。。。')
    time.sleep(3)
    print('执行结束。。', name)


threading.Thread(target=foo, args=('乔布斯',)).start()
print('主线程')
打印结果:

执行。。。
主线程
执行结束。。 乔布斯

# -----------------------------------------
import multiprocessing
import time


def foo(name):
    print('执行。。。')
    time.sleep(3)
    print('执行结束。。', name)


multiprocessing.Process(target=foo, args=('詹姆斯',)).start()
print('主进程')
打印结果:

主进程
执行。。。
执行结束。。 詹姆斯

总结:进程开启多个核(多个cpu)进行,线程仅仅是在单核模式下进行cpu的调度。

2. 多进程之间的通信(队列和管道)

2.1 队列通信

from multiprocessing import Process, Queue   #Queue是进程排列

def f(test):
    test.put('22')   #通过创建的子进程往队列添加数据,实线父子进程交互

if __name__ == '__main__':
    q = Queue()      #父进程
    q.put("11")

    p = Process(target=f, args=(q,))   #子进程
    p.start()
    p.join()

    print("取到:",q.get_nowait())
    print("取到:",q.get_nowait())

#父进程在创建子进程的时候就把q克隆一份给子进程
#通过pickle序列化、反序列化,来达到两个进程之间的交互



结果:
取到: 11
取到: 22

2.2 管道通信

from multiprocessing import Process, Pipe

def f(conn):
    conn.send('11')
    conn.send('22')
    print("from parent:",conn.recv())
    print("from parent:", conn.recv())
    conn.close()

if __name__ == '__main__':
    parent_conn, child_conn = Pipe()   #生成管道实例,可以互相send()和recv()

    p = Process(target=f, args=(child_conn,))
    p.start()

    print(parent_conn.recv())      # prints "11"
    print(parent_conn.recv())      # prints "22"
    parent_conn.send("33")         # parent 发消息给 child
    parent_conn.send("44")
    p.join()

2.3 Manager实现共享数据

from multiprocessing import Manager, Process
import random


def compute(l):
    l.append(sum([random.randint(1, 100) for _ in range(1000000)]))


with Manager() as manage:
    l = manage.list()
    processes = [Process(target=compute, args=(l,)) for _ in range(8)]
    for process in processes:
        process.start()
        process.join()
    print(l)

2.4 进程池

进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进程,那么程序就会等待,直到进程池中有可用进程为止。

进程池中有以下几个主要方法:

  1. apply:从进程池里取一个进程并执行
  2. apply_async:apply的异步版本
  3. terminate:立刻关闭线程池
  4. join:主进程等待所有子进程执行完毕,必须在close或terminate之后
  5. close:等待所有进程结束后,才关闭线程池
from multiprocessing import Process, Pool

import time
import os


def foo(i):  # 操作函数
    time.sleep(2)
    print(i, os.getpid(), 'foo')
    return i * 100


def bar(args):  # 回调函数
    print('execute:', args, os.getpid())


print('主进程', os.getpid())
pool = Pool(processes=3)  # 可以修改进程数目
for i in range(10):
    pool.apply_async(func=foo, args=(i,), callback=bar)  # 回调函数的形参是调用函数的返回值
pool.close()
pool.join()

3. 协程

3.1.简介

协程(Coroutine) : 是单线程下的并发 , 又称微线程 , 纤程 . 协程是一种用户态的轻量级线程 , 即协程有用户自己控制调度

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。

协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态

使用协程的优缺点

优点 :

  1. 协程的切换开销更小 , 属于程序级别的切换 , 更加轻量级
  2. 单线程内就可以实现并发的效果 , 最大限度利用CPU

缺点 :

  1. 协程的本质是单线程下 , 无法利用多核 , 可以是一个程序开启多个进程 , 每个进程内开启多个线程 , 每个线程内开启协程
  2. 协程指的是单个线程 , 因而一旦协程出现阻塞 将会阻塞整个线程
# ------------------------------
from greenlet import greenlet


def foo():
    print(11)
    g2.switch()
    print(12)
    g2.switch()


def fo():
    print(21)
    g1.switch()
    print(22)


g1 = greenlet(foo)
g2 = greenlet(fo)

g1.switch()

# --------------------------
# Gevent 是一个第三方库,可以轻松
# 通过gevent实现并发同步或异步编程,
# 在gevent中用到的主要模式是Greenlet,
# 它是以C扩展模块形式接入Python的轻量级协程。
# TODO 加一个monkey.patch_all() 就可以使用time.sleep()去替换gevent.sleep(),达到IO耗时操作处理
import gevent


def f1():
    print('Runing f1...')
    gevent.sleep(3)
    print('最慢')


def f2():
    print('Runing f2...')
    gevent.sleep(2)
    print('中')


def f3():
    print('Runing f3')
    gevent.sleep(1)
    print('快')


gevent.joinall([
    gevent.spawn(f1),
    gevent.spawn(f2),
    gevent.spawn(f3),
])
# Runing f1...
# Runing f2...
# Runing f3
# 快
# 中
# 最慢

# ---------------------------
import requests
import gevent
import re


def get_page(page, result_list):
    url = 'http://maoyan.com/board/4?offset=' + str(page)
    headers = {
        "User-Agent": "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)"
    }

    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        html = response.content.decode('utf-8')
        movie_names = parse_page(html)
        result_list.append(movie_names)


def parse_page(html):
    # 片名
    pattern = re.compile('movieId.*?>.*?<img.*?<img.*?alt="(.*?)" class.*?', re.S)
    movie_names = re.findall(pattern, html)
    return movie_names


result_list = []
gevent_list = [gevent.spawn(get_page, page * 10, result_list) for page in range(10)]
gevent.joinall(gevent_list)

print(result_list)

版权声明:本文来源简书,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://www.jianshu.com/p/c70d51aee9e8
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-01-12 13:03:23
  • 阅读 ( 1131 )
  • 分类:Linux

0 条评论

请先 登录 后评论

官方社群

GO教程

推荐文章

猜你喜欢