【杂谈】并发和并行、同步和异步、阻塞和非阻塞、IO模型_阻塞iocpu和io设备并行

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Golang全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注go)
img

正文

\color{#0636e5}{阻塞和非阻塞}

阻塞和非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.

阻塞

阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。

非阻塞

非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

还是上面的例子,
你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。
在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。

用这个书店例子来说:
同步阻塞:你打电话问老板有没有某书,老板去查,在老板给你结果之前,你一直拿着电话等待老板给你结果,你此时什么也干不了。
同步非阻塞:你打电话过去后,在老板给你结果之前,你拿着电话等待老板给你结果,但是你拿着电话等的时候可以干一些其他事,比如嗑瓜子。
异步阻塞:你打电话过去后,老板去查,你挂掉电话,等待老板给你打电话通知你,这是异步,你挂了电话后还是啥也干不了,只能一直等着老板给你打电话告诉你结果,这是阻塞。
异步非阻塞:你打电话过去后,你就挂了电话,然后你就想干嘛干嘛去。只用时不时去看看老板给你打电话没。

I

O

\color{#3ae2ea}{IO模型}

IO模型

阻塞IO

在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样:
在这里插入图片描述
当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。

​而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。

所以,blocking IO的特点就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了。

​几乎所有的程序员第一次接触到的网络编程都是从listen()、send()、recv() 等接口开始的,使用这些接口可以很方便的构建服务器/客户机的模型。然而大部分的socket接口都是阻塞型的。如下图

​ ps:所谓阻塞型接口是指系统调用(一般是IO接口)不返回调用结果并让当前线程一直阻塞,只有当该系统调用获得结果或者超时出错时才返回。

实际上,除非特别指定,几乎所有的IO接口 ( 包括socket接口 ) 都是阻塞型的。这给网络编程带来了一个很大的问题,如在调用recv(1024)的同时,线程将被阻塞,在此期间,线程将无法执行任何运算或响应任何的网络请求。

一种解决方案

在服务器端使用多线程(或多进程)。多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接。
这种方案的问题
开启多进程或都线程的方式,在遇到要同时响应成百上千路的连接请求,则无论多线程还是多进程都会严重占据系统资源,降低系统对外界响应效率,而且线程与进程本身也更容易进入假死状态。

改进的方案

很多程序员可能会考虑使用“线程池”或“连接池”。“线程池”旨在减少创建和销毁线程的频率,其维持一定合理数量的线程,并让空闲的线程重新承担新的执行任务。“连接池”维持连接的缓存池,尽量重用已有的连接、减少创建和关闭连接的频率。这两种技术都可以很好的降低系统开销,都被广泛应用很多大型系统,如websphere、tomcat和各种数据库等。
存在的问题
#“线程池”和“连接池”技术也只是在一定程度上缓解了频繁调用IO接口带来的资源占用。而且,所谓“池”始终有其上限,当请求大大超过上限时,“池”构成的系统对外界的响应并不比没有池的时候效果好多少。所以使用“池”必须考虑其面临的响应规模,并根据响应规模调整“池”的大小。

非阻塞IO

Linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:
在这里插入图片描述

从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是用户就可以在本次到下次再发起read询问的时间间隔内做其他事情,或者直接再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存(这一阶段仍然是阻塞的),然后返回。

​也就是说非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。

​ 所以,在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有。
案例代码

服务端

import socket
import time

server=socket.socket()
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind((‘127.0.0.1’,8083))
server.listen(5)

server.setblocking(False)
r_list=[]
w_list={}

while 1:
try:
conn,addr=server.accept()
r_list.append(conn)
except BlockingIOError:

强调强调强调:!!!非阻塞IO的精髓在于完全没有阻塞!!!

time.sleep(0.5) # 打开该行注释纯属为了方便查看效果

print(‘在做其他的事情’)
print('rlist: ',len(r_list))
print('wlist: ',len(w_list))

遍历读列表,依次取出套接字读取内容

del_rlist=[]
for conn in r_list:
try:
data=conn.recv(1024)
if not data:
conn.close()
del_rlist.append(conn)
continue
w_list[conn]=data.upper()
except BlockingIOError: # 没有收成功,则继续检索下一个套接字的接收
continue
except ConnectionResetError: # 当前套接字出异常,则关闭,然后加入删除列表,等待被清除
conn.close()
del_rlist.append(conn)

遍历写列表,依次取出套接字发送内容

del_wlist=[]
for conn,data in w_list.items():
try:
conn.send(data)
del_wlist.append(conn)
except BlockingIOError:
continue

清理无用的套接字,无需再监听它们的IO操作

for conn in del_rlist:
r_list.remove(conn)

for conn in del_wlist:
w_list.pop(conn)

#客户端
import socket
import os

client=socket.socket()
client.connect((‘127.0.0.1’,8083))

while 1:
res=(‘%s hello’ %os.getpid()).encode(‘utf-8’)
client.send(res)
data=client.recv(1024)

print(data.decode(‘utf-8’))

但是非阻塞IO模型绝不被推荐。
​我们不能否则其优点:能够在等待任务完成的时间里干其他活了(包括提交其他任务,也就是 “后台” 可以有多个任务在“”同时“”执行)。
但是也存在问题
#1. 循环调用recv()将大幅度推高CPU占用率;这也是我们在代码中留一句time.sleep(2)的原因,否则在低配主机下极容易出现卡机情况
#2. 任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。

多路复用IO

IO multiplexing这个词可能有点陌生,但是如果我说select/epoll,大概就都能明白了。有些地方也称这种IO方式为事件驱动IO(event driven IO)。我们都知道,select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。它的流程如图:
在这里插入图片描述
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
这个图和blocking IO的图其实并没有太大的不同,事实上还更差一些。因为这里需要使用两个系统调用(select和recvfrom),而blocking IO只调用了一个系统调用(recvfrom)。但是,用select的优势在于它可以同时处理多个connection。

​强调:

  • ​如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。
  • 在多路复用模型中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。

​ 结论: select的优势在于可以处理多个连接,不适用于单个连接

异步IO

Linux下的asynchronous IO其实用得不多,从内核2.6版本才开始引入。先看一下它的流程:
在这里插入图片描述
用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。

p

y

t

h

o

n

I

O

\color{#e3f92b}{python中异步IO}

python中异步IO

在python中要进行异步编程,必须熟悉协程,之前我们已经专门介绍过详情查看,在协程的实现方法中,重要的一种方法就是asyncio

asyncio

asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。

asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。

用asyncio实现Hello world代码如下:

import asyncio

@asyncio.coroutine
def hello():
print(“Hello world!”)

异步调用asyncio.sleep(1):

r = yield from asyncio.sleep(1)
print(“Hello again!”)

获取EventLoop:

loop = asyncio.get_event_loop()

执行coroutine

loop.run_until_complete(hello())
loop.close()

@asyncio.coroutine把一个generator标记为coroutine类型,然后,我们就把这个coroutine扔到EventLoop中执行。

hello()会首先打印出Hello world!,然后,yield from语法可以让我们方便地调用另一个generator。由于asyncio.sleep()也是一个coroutine,所以线程不会等待asyncio.sleep(),而是直接中断并执行下一个消息循环。当asyncio.sleep()返回时,线程就可以从yield from拿到返回值(此处是None),然后接着执行下一行语句。

把asyncio.sleep(1)看成是一个耗时1秒的IO操作,在此期间,主线程并未等待,而是去执行EventLoop中其他可以执行的coroutine了,因此可以实现并发执行。

我们用Task封装两个coroutine试试:

import threading
import asyncio

@asyncio.coroutine
def hello():
print(‘Hello world! (%s)’ % threading.currentThread())
yield from asyncio.sleep(1)
print(‘Hello again! (%s)’ % threading.currentThread())

loop = asyncio.get_event_loop()
tasks = [hello(), hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

Hello world! (<_MainThread(MainThread, started 19516)>)

Hello world! (<_MainThread(MainThread, started 19516)>)

Hello again! (<_MainThread(MainThread, started 19516)>)

Hello again! (<_MainThread(MainThread, started 19516)>)

由打印的当前线程名称可以看出,两个coroutine是由同一个线程并发执行的。

如果把asyncio.sleep()换成真正的IO操作,则多个coroutine就可以由一个线程并发执行。

asynic/await

用asyncio提供的@asyncio.coroutine可以把一个generator标记为coroutine类型,然后在coroutine内部用yield from调用另一个coroutine实现异步操作。

为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法async和await,可以让coroutine的代码更简洁易读。

请注意,async和await是针对coroutine的新语法,要使用新的语法,只需要做两步简单的替换:

  • 把@asyncio.coroutine替换为async;
  • 把yield from替换为await。

import threading
import asyncio

async def hello():
print(‘Hello world! (%s)’ % threading.currentThread())
await asyncio.sleep(1)
print(‘Hello again! (%s)’ % threading.currentThread())

loop = asyncio.get_event_loop()
tasks = [hello(), hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

源码大致流程

获取当前事件循环 get_event_loop

创建一个任务(Future对象),没绑定任何行为,则这个任务永远不知道什么时候结束 create_future()

创建一个任务(Task对象),绑定了set_after函数,函数内部在2s之后,会给fut赋值。 即手动设置future任务的最终结果,那么fut就可以结束了。create_task(set_after(func))

等待 Future对象获取 最终结果,否则一直等下去 await func

创建协程,将协程封装到一个Task对象中并立即添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。run_until_complete

获取事件循环

def get_event_loop():
“”"Return an asyncio event loop.

When called from a coroutine or a callback (e.g. scheduled with call_soon
or similar API), this function will always return the running event loop.

If there is no running event loop set, the function will return
the result of get\_event\_loop\_policy().get\_event\_loop() call.
“”"

NOTE: this function is implemented in C (see _asynciomodule.c)

current_loop = _get_running_loop()
if current_loop is not None:
return current_loop
return get_event_loop_policy().get_event_loop()

asyncio.wait(task_list)

async def wait(fs, *, loop=None, timeout=None, return_when=ALL_COMPLETED):
“”"Wait for the Futures and coroutines given by fs to complete.

The sequence futures must not be empty.

Coroutines will be wrapped in Tasks.

Returns two sets of Future: (done, pending).

Usage:

done, pending = await asyncio.wait(fs)

Note: This does not raise TimeoutError! Futures that aren’t done
when the timeout occurs are returned in the second set.
“”"
if futures.isfuture(fs) or coroutines.iscoroutine(fs):
raise TypeError(f"expect a list of futures, not {type(fs).__name__}")
if not fs:
raise ValueError(‘Set of coroutines/Futures is empty.’)
if return_when not in (FIRST_COMPLETED, FIRST_EXCEPTION, ALL_COMPLETED):
raise ValueError(f’Invalid return_when value: {return_when}')

if loop is None:
loop = events.get_event_loop()

fs = {ensure_future(f, loop=loop) for f in set(fs)}
return await _wait(fs, timeout, return_when, loop)

_wait The fs argument must be a collection of Futures. 任务对象

async def _wait(fs, timeout, return_when, loop):
“”"Internal helper for wait().

The fs argument must be a collection of Futures.
“”"
assert fs, ‘Set of Futures is empty.’
waiter = loop.create_future()
timeout_handle = None
if timeout is not None:
timeout_handle = loop.call_later(timeout, _release_waiter, waiter)
counter = len(fs)

def _on_completion(f):
nonlocal counter
counter -= 1
if (counter <= 0 or
return_when == FIRST_COMPLETED or
return_when == FIRST_EXCEPTION and (not f.cancelled() and
f.exception() is not None)):
if timeout_handle is not None:
timeout_handle.cancel()
if not waiter.done():
waiter.set_result(None)

for f in fs:
f.add_done_callback(_on_completion)

try:
await waiter
finally:
if timeout_handle is not None:
timeout_handle.cancel()

done, pending = set(), set()
for f in fs:
f.remove_done_callback(_on_completion)
if f.done():
done.add(f)
else:
pending.add(f)
return done, pending

run_until_complete

def run_until_complete(self, future):
“”"Run the event loop until a Future is done.

Return the Future’s result, or raise its exception.
“”"
raise NotImplementedError

Task对象

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
letion)
if f.done():
done.add(f)
else:
pending.add(f)
return done, pending

run_until_complete

def run_until_complete(self, future):
“”"Run the event loop until a Future is done.

Return the Future’s result, or raise its exception.
“”"
raise NotImplementedError

Task对象

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-U5LwmDBE-1713442495415)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 15
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
存储过程的增量和全量是指在数据同步过程中使用的两种不同的方式。 全量同步是指在一定的周期内,将当前系统在周期时间内的所有数据复制到目标表或系统。简单来说,全量同步就是将源数据全部复制到目标表,不考虑数据的变化情况。全量同步是增量同步的前提,因为增量同步是基于全量数据的更新。 增量同步是指在全量同步的基础上,根据一定的规则或条件,只同步更新后的数据。增量同步通常是抓取某个时刻或检查点以后的数据进行同步,而不是无规律地进行全量同步。 对于存储过程的增量和全量,具体的实现方式会根据实际需求和业务系统的特点而有所不同。在某些情况下,可以通过全量方式将业务系统的数据快照保存到数据仓库中,以保留业务每天变化的历史记录。但是这样做会导致数据量庞大,存在大量的冗余数据。另一种方式是通过对比源系统和数据仓库中的数据,找出增量数据进行同步。在这种情况下,需要考虑是否标注删除的数据,并根据需要进行相应的处理。 综上所述,存储过程的增量和全量是数据同步过程中使用的两种不同的方式,全量同步是将源数据全部复制到目标表,而增量同步是根据规则或条件,只同步更新后的数据。具体的实现方式会根据实际需求和业务系统的特点而有所不同。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [增量和全量](https://blog.csdn.net/qq_45106437/article/details/113112370)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [数据仓库实践杂谈(九)——增量/全量](https://blog.csdn.net/cfy_fantasyxx/article/details/103891879)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值