进程,线程,协程

cpu:内核

进程:

优点:提高效率,利用cpu多核优势
开启个数:理论上是cpu内核的1-2倍
描述:一段程序或者脚本的执行,cpu资源分配的最小单位
缺点:资源消耗非常大,进程过多,cpu切换进程执行也消耗资源,资源共享困难
使用场景:cpu密集型应用程序(计算密集型)

线程:

优点:提高效率,资源共享
开启个数:跟计算硬件有关系,跟应用场景有关系,一般高于可开启进程守数
描述:进程下可以开启多个线程,cpu调度的最小单位
缺点:开启个数也不是无限的,如果开启过多,造成进程瘫痪
使用场景:IO(input(response),ouput(request),)密集型应用程序,爬虫成是网络IO,长延时操作

跨平台的进程创建模块(multiprocessing)

multiprocessing模块提供了一个Process类来代表一个进程对象,下面的例子演示了启动一个子进程并等待其结束

步骤:

1、从multiprocessing模块导入Process类

2、进程的操作(1)创建和启动创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动,这样创建进程比fork()还要简单。创建格式:p=Process(target=函数名)启动格式:进程对象名.start()

参数列表:Process([group [, target [, name [, args [, kwargs]]]]])

target:表示这个进程实例所调用对象;

args:表示调用对象的位置参数元组;

kwargs:表示调用对象的关键字参数字典;

name:为当前进程实例的别名;

group:大多数情况下用不到;

# 在windows下  一定要加   __name__ == '__main__

#单进程代码:

#导入进程模块
import multiprocessing
import os
# 自定义进程
def foo():
    print('开启进程:%s'%os.getpid())
if __name__ == '__main__':
    print('Process:%s' % os.getpid())
    #创建一个进程,则先需要定义一个函数
    p = multiprocessing.Process(target=foo)
    p.start()
#结果
Process:6484
开启进程:6872

#参数的使用

#导入进程模块
import multiprocessing
import os
# 自定义进程
def foo(filename,type,**kwargs):
    print('开启进程:%s'%os.getpid())
    if kwargs['device'] == 'iphonex':
        path = 'www.jd.com/iphonex'
    print('下载:%s.%s.%s'%(path,filename,type))
    print(os.name)
if __name__ == '__main__':
    print('Process:%s' % os.getpid())
    #创建一个进程,则先需要定义一个函数
    p = multiprocessing.Process(target=foo,name='下载', args=('log','png'),kwargs={'device':'iphonex'})
    #别名
    print(p.name)
    p.start()

#结果
Process:7768
下载
开启进程:9272
下载:www.jd.com/iphonex.log.png
nt

#多进程

import multiprocessing  # 导入多进程模块
# 定义进程函数
def foo(num):
    print('hello %d号进程' % num)
# 在windows下如果创建多进程则需要在以下条件成立
if __name__ == '__main__':
    # 创建一个进程,则需要先定义一个函数
    # target : 进程函数对象
    # args : 进程函数的参数
    for i in range(10):
        p = multiprocessing.Process(target=foo,args=(i,))
        p.start()
#结果
hello 0号进程
hello 4号进程
hello 3号进程
hello 8号进程
hello 7号进程
hello 1号进程
hello 5号进程
hello 9号进程
hello 6号进程
hello 2号进程

多进程资源共享

import multiprocessing  # 导入多进程模块
from multiprocessing import Manager


# 定义进程函数
def foo(num,ls):
    ls.append(num)
    print('hello %d号进程' % num)


# 在windows下如果创建多进程则需要在以下条件成立
if __name__ == '__main__':
    # 创建一个进程,则需要先定义一个函数
    # target : 进程函数对象
    # args : 进程函数的参数
    m = Manager() # 初始化一个manager对象
    ls = m.list() # 创建共享数据类型


    process_list = []
    for i in range(10):
        p = multiprocessing.Process(target=foo,args=(i,ls))
        p.start() # 启动一个进程
        process_list.append(p) # 进程执行中会阻塞主进程


    for p in process_list:
        p.join()


    # 需要等待10个进程全部执行完毕再继续执行主进程
    print('进程执行完毕,执行我')
    print(ls)
#结果
hello 7号进程
hello 3号进程
hello 2号进程
hello 8号进程
hello 6号进程
hello 0号进程
hello 4号进程
hello 9号进程
hello 5号进程
hello 1号进程
进程执行完毕,执行我
[7, 3, 2, 8, 6, 0, 4, 9, 5, 1]

进程池的使用

当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法。初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执行,请看下面的实例:

# 1-50-1号进程   51-100-2号进程
import time
# 进程池
from multiprocessing import Pool

def foo(num):
    time.sleep(1)
    print(num)

if __name__ == '__main__':
    # 创建一个进程池
    pool = Pool(16)
    for i in range(100):
        # 创建一个进程
        pool.apply_async(func=foo,args=(i,))
    # 进程池中所有进程执行完毕,继续执行
    pool.close()
    pool.join()

腾讯招聘多进程爬虫

import multiprocessing
from multiprocessing import Manager
from queue import Queue
import time
import requests

# 任务函数,把要执行的任务全部封装到这个函数中
def foo(task_q):
    # 如果不为空
    while not task_q.empty():
        #取出对列里的内容
        fullurl = task_q.get()
        #请求头
        headers = {
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36'
        }
        #请求页面
        response = requests.get(fullurl,headers=headers)
        print(response.url)
if __name__ == '__main__':
    print(time.ctime())

    # 生成任务队列
    manager = Manager()
    task_q = manager.Queue()
    base_url = 'https://hr.tencent.com/position.php?start=%d'
    for i in range(0,3900 + 1,10):
        # 创建进程,计划了一个任务
        fullurl = base_url % i
        #加入对列
        task_q.put(fullurl)
    p_list = []
    # 创建的进程数,并发数16
    for i in range(16):
        # 创建进程,计划了一个任务
        p = multiprocessing.Process(target=foo,args=(task_q,))
        # 把计划任务执行起来
        p.start()
        # p.join()
        p_list.append(p)
    for p in p_list:
        #等待进程实例执行结束
        p.join()
    print(time.ctime())

多线程基本使用

import threading # 导入线程模块
import time

# 线程函数
def foo(num):
    time.sleep(2)
    print(num)

# 创建一个线程
t1 = threading.Thread(target=foo,args=(1,))
t2 = threading.Thread(target=foo,args=(2,))

# 启动线程
t1.start()
t2.start()
-

import threading
import time

def foo(i):
    print('%d号线程启动' % i)
    time.sleep(2)
    print('%d号线程结束' % i)

thread_list = []
for i in range(1,10):
    t = threading.Thread(target=foo,args=(i,))
    t.start()
    print(t.is_alive())
    thread_list.append(t)

# 等待所有线程执行完毕
for t in thread_list:
    t.join()

# 查看所有线程状态
for t in thread_list:
    print(t.is_alive())

线程类的使用

import threading # 导入线程模块
import time

# 线程函数
def foo(num):
    time.sleep(2)
    print(num)

# 创建一个线程
t1 = threading.Thread(target=foo,args=(1,))
t2 = threading.Thread(target=foo,args=(2,))

# 启动线程
t1.start()
t2.start()
-

import threading
import time

def foo(i):
    print('%d号线程启动' % i)
    time.sleep(2)
    print('%d号线程结束' % i)

thread_list = []
for i in range(1,10):
    t = threading.Thread(target=foo,args=(i,))
    t.start()
    print(t.is_alive())
    thread_list.append(t)

# 等待所有线程执行完毕
for t in thread_list:
    t.join()

# 查看所有线程状态
for t in thread_list:
    print(t.is_alive())

互斥锁

from threading import Thread,Lock
g_num =0
def w1():
    global g_num
    for i in range(1000000):
        #开锁
        mut = mutex.acquire(True)
        # 判断是否有线程在用
        if mut:
            g_num += 1
            #解锁
            mutex.release()
    print(g_num)
def w2():
    global g_num
    for i in range(1000000):
        #开锁
        mut = mutex.acquire(True)
        #判断是否有线程在用
        if mut:
            g_num += 1
            #解锁
            mutex.release()
    print(g_num)
if __name__ == '__main__':
    #创建锁
    mutex = Lock()
    t1 = Thread(target=w1)
    t1.start()
    t2 = Thread(target=w2)
    t2.start()

上锁解锁过程

当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态

总结

锁的好处:

            确保了某段关键代码只能由一个线程从头到尾完整;

地执行锁的坏处:

            阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁

死锁

在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。尽管死锁很少发生,但一旦发生就会造成应用的停止响应。下面看一个死锁的例子:有两个人分别做“西兰花”和“红烧肉”,每个人都需要“铲子”和“锅”之后才能炒菜。

代码

import threading
class XiLanHua(threading.Thread):
    def run(self):
        if mutex_A.acquire():
            print(self.name,'拿到了铲子')
            if mutex_B.acquire():
                print(self.name,'拿到了锅')
                mutex_B.release()
            mutex_A.release()
            print(self.name,'使用完了')
class HongShaoRui(threading.Thread):
    def run(self):
        if mutex_B.acquire():
            print(self.name,'拿到了锅')
            #判断锁的状态
            if mutex_A.acquire():
                print(self.name, '拿到了铲子')
                mutex_A.release()
            mutex_B.release()
            print(self.name, '使用完了')

if __name__ == '__main__':
    #创建两把锁
    mutex_A = threading.Lock()
    mutex_B = threading.Lock()
    cook1 = XiLanHua()
    cook2 = HongShaoRui()
    cook1.start()
    cook2.start()

避免死锁

程序设计时要尽量避免(银行家算法)

添加超时时间

线程池

from queue import Queue
import threading
from multiprocessing import Pool
import time
class ThreadPool(object):
    def __init__(self,max_num):
        # 设置线程队列
        self.thread_q = Queue(max_num)

        # 往队列里添加线程类对象
        for i in range(max_num):
            self.thread_q.put(threading.Thread)

    # 获取一个线程
    def get_thread(self):
        return self.thread_q.get()
    # 添加一个线程
    def add_thread(self):
        self.thread_q.put(threading.Thread)
# 使用线程池
def foo(i,pool):
    print(i)
    time.sleep(1)
    pool.add_thread()
if __name__ == '__main__':
    # 创建线程池
    pool = ThreadPool(8)

    # 循环创建任务
    for i in range(1,11):
        # 从线程池拿取一个线程
        Thread = pool.get_thread()
        # 创建线程任务
        t = Thread(target=foo,args=(i,pool))
        # 启动任务
        t.start()

线程类的爬虫

import threading
from queue import Queue
import requests
from urllib import request
from lxml import etree
import time
import random
import json

concurrent = 3
conparse = 3

# 解析类
class Parse(threading.Thread):
    def __init__(self,number,data_q,thread_list,f):
        super(Parse, self).__init__()
        self.number = number
        self.data_q = data_q
        self.thread_list = thread_list
        self.f = f
        self.is_parse = True


    def run(self):
        while True:
            for t in self.thread_list:
                #如果为死线程就退出
                if t.is_alive():
                    break
            else:
                if self.data_q.qsize() == 0:
                    self.is_parse = False
            if self.is_parse:
                try:
                    html = self.data_q.get(timeout=3)
                    self.parse(html)
                except Exception as e:
                    pass
            else:
                break

        print('%d号解析线程结束' % self.number)



    def parse(self,html):
        html = etree.HTML(html)
        # 获取段子div
        duanzi_div = html.xpath('//div[@id="content-left"]/div')

        # 循环所有段子,获取详细内容
        for duanzi in duanzi_div:
            nick = duanzi.xpath('.//h2/text()')[0].strip()
            age = duanzi.xpath('.//div[@class="articleGender manIcon"]/text() | .//div[@class="articleGender womenIcon"]/text()')
            if age:
                age = age[0]
            else:
                age = 0
            gender_cls = duanzi.xpath('.//div[@class="articleGender manIcon"]/@class | .//div[@class="articleGender womenIcon"]/@class')
            if gender_cls:
                if 'man' in gender_cls[0]:
                    gender = '男'
                elif 'women' in gender_cls[0]:
                    gender = '女'
            else:
                gender = '中'
            content = duanzi.xpath('.//div[@class="content"]/span/text()')
            content = ''.join(content).strip()
            # 星星和评论
            starts = duanzi.xpath('.//span[@class="stats-vote"]/i/text()')[0]
            comments = duanzi.xpath('.//span[@class="stats-comments"]//i/text()')[0]
            data = {
                'nick' : nick,
                'age' : age,
                'gender' : gender,
                'content' : content,
                'starts' : starts,
                'comments' : comments
            }
            # 获取图片并下载
            duanzi_img = duanzi.xpath('//div[@class="thumb"]//img/@src')
            if duanzi_img: # 段子有图片
                duanzi_img = duanzi_img[0]

                duanzi_img = 'https:' + duanzi_img
                fname = duanzi_img.split('/')[-1]
                request.urlretrieve(duanzi_img,'./image/' + fname)
            else:
                duanzi_img = ''
            # 存储
            self.f.write(json.dumps(data,ensure_ascii=False) + '\n')

# 采集类
class Crawl(threading.Thread):
    proxies = [
        {"host": "61.155.164.111", "port": "3128"},
        {"host": "122.72.18.34", "port": "80"}
    ]
    def __init__(self,number,task_q,data_q):
        super(Crawl, self).__init__()
        self.number = number
        self.task_q = task_q
        self.data_q = data_q
        print(self.number)
    # 如果线程调用start方法就调用run方法
    def run(self):
        while not self.task_q.empty():
            fullurl = task_q.get()
            print('%d号线程采集%s' % (self.number,fullurl))

            # 随机获取一个代理
            ran_proxy = random.choice(self.proxies)

            proxy = {
                'http' : 'http://' + ran_proxy['host'] + ':' + ran_proxy['port'],
                'https' : 'http://' + ran_proxy['host'] + ':' + ran_proxy['port']
            }

            response = requests.get(url=fullurl)
            # print(response.status_code) # 获取响应状态
            if 200 <= response.status_code <= 300:
                html = response.text
                self.data_q.put(html)
            else:
                print('响应内容错误')
            # time.sleep(random.random() * 3)

if __name__ == '__main__':
    # 生成请求队列
    task_q = Queue()

    # 数据队列
    data_q = Queue()

    # 生成文件对象
    f = open('duanzi.json','w',encoding='utf-8')

    base_url = 'https://www.qiushibaike.com/8hr/page/%d/'
    for page in range(1, 13 + 1):
        fullurl = base_url % page
        task_q.put(fullurl)

    thread_list = []
    for i in range(concurrent):
        t = Crawl(i + 1,task_q,data_q)
        t.start()
        thread_list.append(t)

    # 启动解析线程
    parse_thread = []
    for i in range(conparse):
        t = Parse(i + 1 ,data_q,thread_list,f)
        t.start()
        parse_thread.append(t)

    # 等待所有采集线程运行完毕
    for t in thread_list:
        t.join()

    # 等待所有解析线程运行完毕
    for t in parse_thread:
        t.join()

    f.close()

ThreadLocal

在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的修改必须加锁。

import threading
local_school = threading.local()
class Student():
    def __init__(self,name):
        self.name = name
def process_student(name):
    std = Student(name)
    local_school.student = std
    do_task_1()
    do_task_2()
def do_task_1():
    std = local_school.student
    print('do_task_1',std.name)
def do_task_2():
    std = local_school.student
    print('do_task_2',std.name)
if __name__ == '__main__':
    t1 = threading.Thread(target=process_student,args=('小明',))
    t2 = threading.Thread(target=process_student, args=('小hong',))
    t2.start()
    t1.start()
#结果
 do_task_1 小hong
do_task_2 小hong
do_task_1 小明
do_task_2 小明

全局变量local_school就是一个ThreadLocal对象,每个Thread对它都可以读写student属性,但互不影响。你可以把local_school看成全局变量,但每个属性如local_school.student都是线程的局部变量,可以任意读写而互不干扰,也不用管理锁的问题,ThreadLocal内部会处理。可以理解为全局变量local_school是一个dict,不但可以用local_school.student,还可以绑定其他变量,如local_school.teacher等等。ThreadLocal最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。

一个ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰。ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题。


协程

首先我们得知道协程是啥?协程其实可以认为是比线程更小的执行单元。 为啥说他是一个执行单元,因为他自带CPU上下文。这样只要在合适的时机, 我们可以把一个协程 切换到另一个协程。 只要这个过程中保存或恢复 CPU上下文,那么程序还是可以运行的。通俗的理解:在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外个函数中执,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者确定。

在IO密集型的程序中由于IO操作远远慢于CPU的操作,所以往往需要CPU去 等IO操作。 同步IO下系统需要切换线程,让操作系统可以在IO过程中执行其他的东西。这样虽然代码是符合人类的思维习惯但是由于大量的线程切换带 来了大量的性能的浪费,尤其是IO密集型的程序。所以人们发明了异步IO。就是当数据到达的时候触发我的回调。来减少线程 切换带来性能损失。 但是这样的坏处也是很大的,主要的坏处就是操作被 “分片” 了,代码写的不是 “一气呵成” 这种。而是每次来段数据就要判断数据够不够处理哇,够处理就处理吧,不够处理就在等等吧。这样代码的可读性很低,其实也不符合人类的习惯。但是协程可以很好解决这个问题。例如把一个IO操作 写成一个协程。当触发 IO操作的时候就 动让出CPU给其他协程。要知道协程的切换很轻的。 协程通过这种对异步IO的封装 既保留了性能也保证了代码的容易编写和可读性。

from gevent import monkey
monkey.patch_all()

import gevent
import requests
from queue import Queue
import greenlet
import time

headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36'
}

def getPage(task_q):
    # print('启动')
    # print(gevent.getcurrent())
    # response = requests.get(task_q,headers=headers)
    # print(response.status_code)
    while not task_q.empty():
        fullurl = task_q.get()
        response = requests.get(fullurl,headers=headers)
        # gevent.sleep(1)
        print(response.url)

print(time.ctime())

base_url = 'https://hr.tencent.com/position.php?start=%d'
# 创建任务
task_q = Queue()
for i in range(0, 3901, 10):
    fullurl = base_url % i
    task_q.put(fullurl)

g_list = []
for i in range(35):
    g = gevent.spawn(getPage,task_q)
    g_list.append(g)

gevent.joinall(g_list)

print('结束运行')
print(time.ctime())

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值