并发编程基础(三)

进程与线程的比较

进程:就是资源分配的最小单位。

线程:就是cpu调度的最小单位

进程比线程的开销更大,进程之间的数据是相互隔离的,线程之间的数据不隔离。但是让进程通信。进程下的线程也通信了,例如使用队列

刚看了个文章很好地说明了进程与线程之间的关系。

进程就好比是火车,线程就是火车的车厢。线程是在进程下进行的,进程下至少有一个线程(火车至少有一个车厢,只有车厢也是无法运行的)。一个进程可以包含多个线程(一辆火车上可以有很多车厢)。不同进程间的数据很难共享,而同一进程下的线程数据共享(一列火车上的乘客很难换到另一辆火车,如果使用队列可以。同一火车下的乘客从1车厢换到2车厢就可以)。进程要比线程消耗更多的计算机资源(采用多个列车运行比多个车厢更加消耗资源 )。进程之间不会相互影响,一个线程挂掉会影响整个进程(一列火车不会影响另一例火车,但是如果火车下的车厢出了问题,整个火车都出问题)。进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)。进程使用的内存地址可以上锁,即一个线程在使用某些共享内存时候,其他线程不能使用,必须等他结束。比如互斥锁---火车的洗手间。进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”

GIL全局解释器锁

GIL是什么?

全局解释器锁(GIL)是Python解释器的一项机制,为了解决Python线程加锁问题而引入。也就是说,Python在启动时就会默认启动一个GIL,该GIL是一根系统级别的互斥锁。GIL保证在任何时候只有一个Python线程能够被执行,Python解释器在执行线程时,会锁住所有线程,使得只有其中的一个线程能够被执行。

为什么要有GIL?

在多线程编程中,我们会遇到许多竞争条件的问题,比如临界区、死锁等问题。对于这些问题,我们需要使用线程锁来防止多个线程同时访问共享资源和数据。因此,在单核CPU的机器上,Python的全局解释器锁比较适合。它可以帮助防止多线程之间对同一个内存区域的竞争。如果没有GIL,多个线程同时操作Python代码的内存数据会导致数据混乱和异常。

GIL会带来的问题?

虽然GIL有助于避免线程之间可能存在的竞争条件,但同时也会带来多个线程之间不恰当的竞争问题。比如,当一个线程想要调用一些I/O密集型的操作或进行计算时,即使有更多的线程处于空闲状态,他们也不能参与到运算中,从而导致CPU资源的浪费,也严重影响了Python的多线程执行效率。

GIL的一些背景信息

1.因为python在解释器上运行,所以需要解释器来解释执行

2.python解释器的种类:CPython,TPython,Jython,PyPy,IronPython

3.当前使用最多的是CPython解释器

4.GIL全局解释器锁是存在CPython中的

5.由于想要避免多个线程同时抢夺资源的情况,所以在解释器上添加了这把锁,这把锁是为了让同一时刻只有一个线程在执行,别的线程想执行就必须拿到这把锁(GIL),只有等这个线程把GIL锁释放掉,别的线程拿到才能具备执行权限

以下是总结的点

1.python有GIL锁的原因,同一个进程下多个线程实际上同一时刻,只有一个线程在执行

2.只有在python上开进程多,其他语言一般不开多进程,只开多线程

3.CPython解释器开多线程不能利用多核优势,而只有多进程才能利用多核优势,其他语言不存在这个问题

4.对于8核cpu的计算机,要充分利用这8核,至少要起8个线程,8条线程都是计算,cpu李永乐就是100%

5.如果不存在GIL锁,一个进程下开启8个线程,就可以充分使用cpu

6.CPython解释器中很多代码模块都是基于GIL锁机制写的 

7.CPython解释器:io密集型使用多线程,假设开8个线程,每个线程都有io操作,遇到io操作就会切换cpu,而io操作不会占太多资源,一段时间内看上去,就是8个线程都执行了,选多线程会好些。计算密集型使用多进程,消耗cpu,如果开了8个线程,第一个线程会一直占着cpu,而不会调度到其他线程执行,其他7个线程根本没执行,所以我们开8个进程,每个进程有一个线程,8个进程下的线程会被8个cpu执行,从而效率高。所以计算密集型选多进程好一些,在其他语言中,都是选择多线程,而不选择多进程。

互斥锁

什么是互斥锁?

就是对共享数据进行锁定,保证同一时刻只能有一个线程去操作。

注意: 互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程需要等待,等互斥锁使用完释放后,其它等待的线程再去抢这个锁。

互斥锁的使用

threading模块中定义了Lock变量,这个变量本质上是一个函数,通过调用这个函数可以获取一把互斥锁。

import time
from threading import Lock
n = 10
def task(lock):
    lock.acquire()
    global n
    temp = n
    time.sleep(0.5)
    n = temp - 1
    lock.release()
from threading import Thread
if __name__ == '__main__':
    l = []
    lock = Lock()
    for i in range(10):
        t = Thread(target=task,args=(lock,))
        t.start()
        l.append(t)
    for j in l:
        j.join()
    print('主',n)

 既然有了GIL锁,为什么还要互斥锁呢?(多线程下)

假如现在有两个线程,来执行a=a+1,a一开始是0.

这时第一个线程来啦,拿到a=0,开始执行a=a+1,这个时候结果a就是1了。第一个线程的结果1还没有重新赋值给a,又来的第二个线程,拿到a=0,继续执行结果a还是1.

加了互斥锁就能够解决多线程下操作同一个数据,发生错乱的问题

线程队列

队列是一种数据结构,它类似于列表。但列表是线程不安全的,而队列是线程安全的。

python的queue(python3,python2为Queue)提供了3种队列:

  • Queue:先进先出型(First In First Out)。
  • LifoQueue:后进先出型(Last In First Out)。
  • PriorityQueue:优先级型,为队列中的元素自定义优先级,按照优先级获取元素。

每种队列的构造方法都有一个maxsize的属性,默认为0,该属性用来指定队列存放的最大元素个数。如果为0或小于0,则队列是无限长的。

队列中的方法:

q.put(10)将某个元素放到队列中。该方法中的参数item(即要存放的元素)是必须指定的;

q.get():对于Queue创建的队列来说,该方法会从队列头部获取并删除一个元素(最先进入队列的元素)。

q.qsize():返回队列的长度;

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

q.full():如果队列满了,返回True,否则返回False。

参数block=True,该参数是否采取阻塞的方式向队列中放置元素

参数timeout=None,当该参数被设置为x(大于或等于0)时,put方法最多会阻塞x秒,之后尝试向队列中存放元素,如果队列已满,则会抛出异常

为什么线程中还有使用队列?

同一个进程下多个线程数据是共享的因为队列是 管道 + 锁,所以用队列还是为了保证数据的安全

##先进先出
from multiprocessing import Queue
import queue
q = queue.Queue()#无限大

q.put('first')
q.put('second')

print(q.get())
print(q.get())


##后进先出
q=queue.LifoQueue()
q.put('first')
q.put('second')
q.put('third')

print(q.get())
print(q.get())
print(q.get())

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

print(q.get())
print(q.get())
print(q.get())
#(10, 'b')
#(20, 'a')
#(30, 'c')

进程池与线程池

什么是池?就是一种容器,可以盛放多个元素

进程池就是提前定义好一个池子,然后往这个池子中添加进程,后面再向这个池子中添加任务就行了,池中的任意一个进程就会执行任务

线程池同理。

为什么要有这种池呢?

比如TCP服务端实现并发的效果,来一个人就开始一个进程或线程服务它。但是这存在一个问题,假设有一亿个客户端同时来访问这个服务器,它能开一亿个进程或线程吗?无论是开设进程还是线程都需要消耗资源,只是开设线程的资源会比进程小一点。但是也架不住来一个开一个,瓶颈是计算机硬件资源无法跟上。

所以,我们的宗旨是:保证硬件能够正常工作的情况下,最大限度的利用资源。


#开两个任务
def task(n,m):
    return n+m

def task1():
    return {'name':'kk','pwd':123}

#开进程池
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

def callback(res):
    print(res) <Future at 0x1e9fb0eb470 state=finished returned int>
    print(res.result()) # 3
def callback1(res):
    print(res) #<Future at 0x1e9fb327a58 state=finished returned dict>
    print(res.result()) #{'name': 'kk', 'pwd': 123}
    print(res.result().get('name')) #kk

if __name__ == '__main__':
    #定义一个进程池,里面有3个进程
    pool = ProcessPoolExecutor(3)

    #向池子中开始放任务
    pool.submit(task,m=1,n=2).add_done_callback(callback)
    pool.submit(task1).add_done_callback(callback1)
    pool.shutdown() #相当于# join + close
    print('主')

协程

协程是什么?

协程是python个中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元(理解为需要的资源)。 为啥说它是一个执行单元,因为它自带CPU上下文。这样只要在合适的时机, 我们可以把一个协程 切换到另一个协程。 只要这个过程中保存或恢复 CPU上下文那么程序还是可以运行的。

简单实现协程

import time
def test1():
    while True:
        print("--test1--")
        yield
        time.sleep(0.5)
def test2():
    while True:
        print("--test2--")
        yield
        time.sleep(0.5)

if __name__ == "__main__":
    t1 = test1()
    t2 = test2()
    while True:
        next(t1)
        next(t2)

greenlet

安装:pip install greenlet

协程实现高并发

服务端:

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()

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值