40.python全栈之路:进程与线程

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了40.python全栈之路:进程与线程相关的知识,希望对你有一定的参考价值。

进程与线程

1.什么是进程,什么是线程?

    进程,是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竟争计算机系统资源的基本单位。每一个进程都有一个自己的地址空间,即进程空间或(虚空间)。进程空间的大小 只与处理机的位数有关,一个 16 位长处理机的进程空间大小为 216 ,而32 位处理机的进程空间大小为 232 。进程至少有 5 种基本状态,它们是:初始态,执行态,等待状态,就绪状态,终止状态。

    线程,在网络或多用户环境下,一个服务器通常需要接收大量且不确定数量用户的并发请求,为每一个请求都创建一个进程显然是行不通的,——无论是从系统资源开销方面或是响应用户请求的效率方面来看。因此,操作系统中线程的概念便被引进了。线程,是进程的一部分,一个没有线程的进程可以被看作是单线程的。线程有时又被称为轻权进程或轻量级进程,也是 CPU 调度的一个基本单位。

2.进程与线程的区别?

    进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对

  其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一

  个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但

  对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

3.全局解释器锁(GIL)

    在进程上加一把锁,为了锁住多个线程,只要多个线程中的其中一个去访问了CPU,那么久锁住进程的资源,不允许其他线程访问同一资源

4.使用多线程及其其常用方法

  setDaemon(True):主线程不等待子线程

  join(n):主线程最多等待子线程n秒

import threading
import time

def f1(a1, a2):
    time.sleep(5)
    print(‘666‘)


t1 = threading.Thread(target=f1, args=(123, 111))
t1.start()

t2 = threading.Thread(target=f1, args=(123, 111))
t2.start()

t3 = threading.Thread(target=f1, args=(123, 111))
t3.start()
‘‘‘
程序等待5秒后,3个线程同时输出666
实现并发
‘‘‘

5.线程锁

import threading
import time

globals_num = 0

lock = threading.RLock()  # 实例化一个锁

def Func():
    lock.acquire()  # 获得锁,锁定当前访问资源
    global globals_num
    globals_num += 1
    time.sleep(1)
    print(globals_num)
    lock.release()  # 释放锁,释放当前访问的资源


for i in range(10):
    t = threading.Thread(target=Func)
    t.start()
‘‘‘
休息一秒 输出一个,因为资源被锁定,
只能等资源被释放了才能使用全部变量
不能像上一个例子一样并发
‘‘‘

6.主线程控制子线程的方法(线程间通讯) 

    Event线程间通信最间的机制之一:一个线程发送一个event信号,其他的线程则等待这个信号。

  用于主线程控制其他线程的执行。 Events 管理一个flag,这个flag可以使用set()设置成True或者

  使用clear()重置为False,wait()则用于阻塞,在flag为True之前。flag默认为False。

    Event.wait([timeout]) : 堵塞线程,直到Event对象内部标识位被设为true或超时(如果提供了参数timeout)。

    Event.set() :将标识位设为ture

    Event.clear() : 将标识伴设为false

    Event.isSet() :判断标识位是否为ture

import threading

def do(event):  # 循环生成了10个线程
    print(‘start‘)
    event.wait()   # 线程产生阻塞,默认为flase,等待标志位为true,线程才继续执行
    print(‘execute‘)


event_obj = threading.Event()
for i in range(10):
    t = threading.Thread(target=do, args=(event_obj,))
    t.start()

event_obj.clear()   #falge设置为false
inp = input(‘input:‘)
if inp == ‘true‘:
    event_obj.set()  # flage设置为true

7.创建进程并使用 

#创建进程
import multiprocessing
import time
# 主进程的主线程创建了两个子进程,再由子进程的线程去执行f1方法
def f1(a1):
    time.sleep(2)
    print(a1)
if __name__ == ‘__main__‘:
    t1 = multiprocessing.Process(target=f1, args=(11,))
    # t1.daemon = True
    t1.start()  # 运行进程 在Windows下需要写在__name__ == ‘__main__‘下
    # t1.join()
    t2 = multiprocessing.Process(target=f1, args=(11,))
    # t2.daemon = True
    t2.start()
    print(‘end‘) 

8.进程与线程在资源共享问题上的区别

  进程:

# 进程不能共享数据,拷贝原数据并单独使用
import multiprocessing

li = []

def foo(i):
    li.append(i)
    print(‘say hi‘,li)
if __name__ == ‘__main__‘:
    for i in range(10):
        p = multiprocessing.Process(target=foo, args=(i,))
        p.start()
‘‘‘
say hi [1]
say hi [2]
say hi [0]
say hi [3]
say hi [4]
say hi [5]
say hi [6]
say hi [7]
say hi [8]
say hi [9]
‘‘‘

  线程:

#线程与线程之间的内存是共享的

import threading

li = []

def foo(i):
    li.append(i)
    print(‘say hi‘,li)
if __name__ == ‘__main__‘:
    for i in range(10):
        p = threading.Thread(target=foo, args=(i,))
        p.start()
‘‘‘
say hi [0]
say hi [0, 1]
say hi [0, 1, 2]
say hi [0, 1, 2, 3]
say hi [0, 1, 2, 3, 4]
say hi [0, 1, 2, 3, 4, 5]
say hi [0, 1, 2, 3, 4, 5, 6]
say hi [0, 1, 2, 3, 4, 5, 6, 7]
say hi [0, 1, 2, 3, 4, 5, 6, 7, 8]
say hi [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
‘‘‘  

9.进程间内存共享方法(默认不共享)

# manage.dict()共享数据  特殊的字典,支持进程之间共享数据
from multiprocessing import Process, Manager


def Foo(i, dic):
    dic[i] = 100 + i
    for k, v in dic.items():
        print(k, v)


if __name__ == ‘__main__‘:
    manage = Manager()
    dic = manage.dict()
    # dic = {}
    for i in range(2):
        p = Process(target=Foo, args=(i, dic))
        p.start()
        p.join()  # 避免主进程关闭后,子进程无法连接主进程,无法拿到数据
    print(dic)
‘‘‘
用manage.dict,数据共享
0 100  第一次只有1个数据

0 100  第二次有两个数据哟
1 101
{0: 100, 1: 101}
‘‘‘

‘‘‘
用普通字典,数据不共享
0 100
1 101
{}
‘‘‘

10.进程池与线程池

  进程池:python内部提供

# python提供了进程池

from multiprocessing import Pool

import time

def Foo(i):
    time.sleep(2)
    print(i)

if __name__ == ‘__main__‘:
    pool = Pool(5)  # 最大能装5个进程,用一个加一个,不事先全部创完
    # 申请进程,执行完后放回进程池
    # pool.apply(Foo,(1,))
    # pool.apply_async(func=Foo, args=(i,)).get()   执行完后,告诉我们一下,再放回进程池
    # 执行完Foo方法后,将Foo方法的返回值,赋值给Bar方法的参数
    # Bar方法,我们称之为回调函数

    for i in range(40):
        # pool.apply(Foo,(i,))  apply申请的时候,一个一个申请并执行
        pool.apply_async(func=Foo, args=(i,))  # 并发进行,还能设置回调函数
        print(‘666666666666‘)
    pool.close()
    pool.join()

  low版线程池:缺点在于,线程执行任务后,不能被重复使用,会等待python销毁它

import queue
import threading
import time


class ThreadPool(object):
    def __init__(self, max_num=20):
        self.queue = queue.Queue(max_num)  # 创建一个最大长度为20的队列
        for i in range(max_num):
            self.queue.put(threading.Thread)  # 传递类名,有20个类名的队列,都指向同一个类

    def get_thread(self):
        return self.queue.get()  # 当队列为空的时候,get则等待

    def add_thread(self):
        self.queue.put(threading.Thread)


def func(pool, a1):
    time.sleep(1)
    print(a1)
    pool.add_thread()


p = ThreadPool(10)
for i in range(100):
    thread = p.get_thread()  # ret 等价于 threading.Thread,是个类名
    t = thread(target=func, args=(p, i))  # 创建一个线程,将线程池对象传入func中,在func结束的时候,再将该线程重新放入线程池
    t.start()  # 启动线程


    # 在队列中放类,线程放在内存在 等待销毁,这样是不合理的,我们可以优化,线程继续做第二件事情
    # 在队列里面放任务,让线程不停的取任务,重复利用线程

  

以上是关于40.python全栈之路:进程与线程的主要内容,如果未能解决你的问题,请参考以下文章

Python全栈之路模块----之-----守护进程进程锁队列生产者消费者模式

python全栈脱产第37天------进程池与线程池协程gevent模块单线程下实现并发的套接字通信

Python全栈开发——线程与进程(进程)

Python全栈开发——进程与线程

Python全栈开发——线程与进程的概念

12python全栈之路-并发编程之多进程