线程、进程池、协程

线程

  进程时一个任务的执行过程,在这个过程中实际做事的是进程,线程是开在进程里面的,需要先有进程,在有线程,一个进程中至少有一个线程,当然,也可以有多个线程

进程是资源分配的基本单位,线程是CPU执行的最小单位,每一个进程中至少有一个线程

进程和线程都是由操作系统来调度的,协程它是由程序员来调度的

进程 >>>> 线程 >>>> 协程
资源消耗是最多的 >>>> 线程的资源 >>>> 协程的

在python中我们一般开多进程,而不开多线程,在其他语言中,都是选择开多线程

进程和线程的比较

  1. 进程开销远远大于线程的开销
  2. 进程之间的数据都是隔离的,线程之间的真实数据呢?线程之间的数据都是共享的,严格的来说:同一个进程下的线程之间数据都是共享的
  3. 想让不同进程之间的线程之间的数据共享 ---->还是让进程之间通信 ---->线程之间也通信了 ---->队列

如何开启线程

需要通过模块from threading improt Thread然后实例化再使用

线程的参数

  1. target = task(必要)
    需要进行线程的函数
  2. name = '****'
    线程的名字
  3. args= (, )
    需要传入线程的位置参数
  4. kwargs= {}
    需要传入线程的关键字参数

线程的方法

  1. t.namet.getName()
    获取线程的名字
  2. t.daemon = Truet.setDaemon(true)
    守护线程,主线程结束,子线程也跟着结束
  3. t.start()
    开始线程
  4. t.is_alive()
    查看线程是否存活
  5. t.setName('****')
    设置线程名字

线程模板

from threading import Thread

def task():
   pass

if __name__ == '__main__':
   t = Thread(target=task)
   t.start()

开启多线程

from threading import Thread

def task():
    pass

if __name__ == '__main__':
    lst = []
    for i in range(3):
        t = Thread(target=task)
        t.start()
        lst.append(t)
    
    for i in lst:
        i.join()

GIL全局解释器锁

  1. python代码运行在解释器之上,由解释器来翻译执行
  2. python解释器种类有哪些?

    CPython IPython PyPy Jython

  3. GIL锁存在于CPython解释器中
  4. 市面上目前绝大多数(95%)都使用的是CPython解释器
  5. 起一个垃圾回收线程,在一起正常执行代码的线程,当垃圾回收线程还没回收完毕,起亚线程可能会抢夺资源,这种情况在python设计之处就不允许的
  6. python在设计之初,就在python解释器之上加了一把锁(GIL锁 ),加这个鬼锁的目的是:同一时刻只能有一个线程执行,不能同时有多个线程执行,如果开了多个线程,那么,线程要想有执行权限,必须先拿到这把锁(GIL锁)

重要

  1. python有GIL锁的原因,同一个进程下多个线程实际上同一时刻,只有一个线程在执行
  2. 只要在python上开进程用的多,其他语言一般不开多进程,只开多线程就够了
  3. cpython解释器开多线程不能利用多核优势,只要多开进程才能利用多核优势,其他语言不存在这个问题
  4. 8核cpu电脑,充分利用起我这个8核,至少起8个线程,8条线程全是计算---->计算机cpu使用率是100%
  5. 如果不存在GIL锁,一个进程下,开启8个线程,它能够充分利用cpu资源,跑满cpu
  6. cpython解释器中好多代码,模块都是基于GIL锁机制写起来的,改不了了---->我们不能有8个核,单我们现在只能用一个核—>开多进程—>每个进程下开启的线程,可以呗多个cpu调度执行
  7. cpython解释器:io密集型使用多线程,计算密集型使用多进程
io密集型:
遇到io操作为切换cpu,假设你开了8个线程,8个线程都有io操作 ----> io操作不消耗cpu ----> 一段时间内看上去,其实8个线程都执行了,选多线程好一些
计算密集型:
消耗cpu,如果开了8个线程,第一个线程会一直占着cpu,而不会调度到其他线程执行,其他7个线程根本没有执行,所以我们开8个进程,每个进程都有一个线程,8个进程下的现场会被8个cpu执行,从而效率高

计算密集型选多进程好些,在其他语言中,都是选择多线程,而不选择多进程。

互斥锁

多个线程去操作同一个数据,会出现并发安全问题,怎么解决呢?

加锁

互斥锁:多个线程同时操作同一个数据,会发生数据不安全问题,这时个时候我们给一个线程加上互斥锁之后,必须等待一个线程执行完毕,其他线程才能执行

队列线程

进程之间的数据是隔离的,所以,我们使用了队列来实现进程之间的通信

线程之间的数据是共享的,那么我们为什么还要使用队列呢?
队列的底层其实还是:管道 + 锁
锁就是为了保证数据的安全
线程内部使用队列

进程Queue用于父进程与子进程(或同一父进程中多个子进程)间数据传递
python自己的多个进程之间交换数据或者其他语言(Java)进程queue就无能为力

queue.Queue的缺点是它的实现涉及多个锁和条件变量,因此可能会影响性能和内存效率
只要加锁,比会影响性能和效率!但是好处就是保证数据安全

线程队列的使用

1.先进先出
import queue

q = queue.Queue()
q.put(1)

q.get()
2.先进后出
import queue

# Lifo:last input first output
q = queue.LifoQueue()
q.put(1)

q.get()
3.优先级队列
import queue

q = queue.PriorityQueue()
# put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较)
# 数字越小优先度越高
q.put(('a', 'b'))

q.get()

进程池和线程池

池子:容器,盛放多个元素值
进程池:存放多个线程的
线程池:存放多个线程的

进程池:提前定义一个池子,里面放很多个进程,只需要往池子里丢任务即可,由这个池子里任意一个进程来执行任务

线程池:提前定义一个池子,里面放很多个线程,只需要往池子里丢任务即可,由这个池子里任意一个线程来执行任务

from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor

def task(a, b):  # 进程函数
    return a + b

def callback(res):  # 回调函数
    res = res.result() + 1
    print(res)
    return res   # 可要可不要

if __name__ == '__main__':
    # 定义池子最多进程,数字是几就是几
    pool = ThreadPoolExecutor(5)

    # 往池子里放任务,如过不需要返回值
    # pool.submit(task, 2, 3)

    # 需要返回值则需要一个回调函数,需要提前写好
    pool.submit(task, 2, 3).add_done_callback(callback)
    
    pool.shutdown()   # 进程池运行玩后关闭池子 join + close

进程池爬取网页

用于解决高并发问题,在进程池的范围内,爬取大量的网页会在进程池在面等待进入

import requests
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

def get_page(url):
    res = requests.get(url)  # 爬取网页
    name = url.rsplit('/')[-1] + '.html'
    return {'name': name, 'text': res.content}


def call_back(fut):
    print(fut.result()['name'])
    with open(fut.result()['name'], 'wb') as f:
        f.write(fut.result()['text'])


if __name__ == '__main__':
    pool = ThreadPoolExecutor(2)
    urls = ['http://www.baidu.com', 'http://www.cnblogs.com', 'http://www.taobao.com']
    for url in urls:
        pool.submit(get_page, url).add_done_callback(call_back)
        

协程

进程:进程解决高并发问题

线程:解决高并发问题

协程:它是单线程下的并发,它是程序员级别的,我们来控制如何切换,何时切换
进程和线程是操作系统来控制的,我们控制不了

协程是一种用户态(程序员)的轻量级线程,即协程是由用户程序自己控制调度的

进程的开销 >>>> 线程的开销 >>>> 协程的开销

协程的使用需要借助于第三方模块gevent模块
必须线安装: pip install geven

在协程中遇到io会切换单如果遇见time.sleep()不会切,需换成gevent.sleep()

协程模板

import gevent

def a(aa):
    pass

if __name__ == '__main__':
    # 开启协程,后面直接传参数
    g = gevent.spawn(a, 123)
    g.join()

协程实现高并发程序

服务端
from gevent import monkey;

monkey.patch_all()
import gevent
from socket import socket
# from multiprocessing import Process
from threading import Thread


def talk(conn):
    while True:
        try:
            data = conn.recv(1024)
            if len(data) == 0: break
            print(data)
            conn.send(data.upper())
        except Exception as e:
            print(e)
    conn.close()


def server(ip, port):
    server = socket()
    server.bind((ip, port))
    server.listen(5)
    while True:
        conn, addr = server.accept()
        # t=Process(target=talk,args=(conn,))
        # t=Thread(target=talk,args=(conn,))
        # t.start()
        gevent.spawn(talk, conn)


if __name__ == '__main__':
    g1 = gevent.spawn(server, '127.0.0.1', 8080)
    g1.join()
客户端
import socket
from threading import current_thread, Thread


def socket_client():
    cli = socket.socket()
    cli.connect(('127.0.0.1', 8080))
    while True:
        ss = '%s say hello' % current_thread().getName()
        cli.send(ss.encode('utf-8'))
        data = cli.recv(1024)
        print(data)


for i in range(5000):
    t = Thread(target=socket_client)
    t.start()

你可能感兴趣的:(网络编程和并发编程,python)