偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

以同步方式寫異步代碼

開發(fā) 前端
同步寫異步代碼都是通過暫停執(zhí)行和恢復(fù)執(zhí)行來實現(xiàn)的,Node.js 是通過 await 控制函數(shù)的暫停和繼續(xù)執(zhí)行,但是它不會影響執(zhí)行實體(線程)的狀態(tài),而 Go 是通過實現(xiàn)新的執(zhí)行實體協(xié)程并阻塞協(xié)程實現(xiàn)的。

背景

我們知道函數(shù)是一次性執(zhí)行完的,比如下面的例子。

ounter(lineounter(lineounter(lineounter(line
function run() {
  run1()
  run2()
}

沒辦法執(zhí)行到 run1 時暫停下來,過一會再執(zhí)行 run2,所以碰到異步操作時就會變得下面這種寫法。

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
function run() {
  run1()
  sleep(() => {
     run2()
  }, 1000)
}

這種回調(diào)地獄相信很多開發(fā)者都經(jīng)歷過。幸好現(xiàn)代的很多語言都支持以同步的方式寫異步代碼。

ounter(lineounter(lineounter(lineounter(lineounter(line
function run() {
  run1()
  await sleep(1000)
  run2()
}

這種方式對編寫代碼和理解代碼來說都非常有意義。在不同語言中,具體的實現(xiàn)不太一樣,下面看一下 JS/Node.js 和 Go 中的實現(xiàn)。

JS / Node.js

我們知道 Node.js 是一個基于事件驅(qū)動的單線程應(yīng)用,簡單來說,我們寫的代碼最終就是注冊各種事件,比如超時,網(wǎng)絡(luò)讀寫,當(dāng)事件發(fā)生時我們的回調(diào)就會被執(zhí)行,比如下面的例子。

ounter(lineounter(lineounter(line
setTimeout(function() {
  console.log(1)
}, 1000);

執(zhí)行上面代碼時,Node.js 首先進(jìn)行自身的初始化,然后執(zhí)行 setTimeout 注冊一個定時器,超時后就會執(zhí)行注冊的回調(diào),這種模式從架構(gòu)上比較簡單易懂,但是基于回調(diào)的方式會讓代碼的邏輯變得混亂和難理解,不利于實際的開發(fā)。現(xiàn)在的 JS 已經(jīng)支持了以同步方式寫異步代碼,保證代碼的編寫和執(zhí)行流程和開發(fā)者的思考邏輯保持一致,下面是一個以同步方式寫異步代碼的例子。

ounter(lineounter(lineounter(line
import timer from "timers/promises"
await timer.setTimeout(1000)
console.log(1)

上面代碼中,demo 函數(shù)會停頓 1s 后繼續(xù)執(zhí)行,符合我們寫代碼的邏輯,上面代碼的執(zhí)行過程如下。

  • Node.js 啟動,執(zhí)行 timer.setTimeout 返回一個 Promise,await 該 Promise 時會保存當(dāng)前上下文,然后退出執(zhí)行。
  • 繼續(xù)處理其他任務(wù),但是這里沒有其他任務(wù)了,所以阻塞等待定時器超時時間。
  • 1s 后定時器超時,resolve timer.setTimeout 對應(yīng)的 promise。
  • await 返回,從 await 下面一句代碼繼續(xù)執(zhí)行,輸出 1。

從上面代碼的執(zhí)行流程中可以看到,在 Node.js 中執(zhí)行一塊代碼時,如果碰到 await 會保存上下文然后退出執(zhí)行,等待 await 后面的 promise 決議后再恢復(fù)執(zhí)行,從中可以看到 await 只會阻塞其后面代碼的執(zhí)行,而不會阻塞整個線程的執(zhí)行??匆粋€實際的例子。

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
import timer from "timers/promises"
import http from "http"


http.createServer(async function handler(req, res) {
   await timer.setTimeout(1000)
   res.end();
}).listen(12345, function() {
    http.get("http://localhost:12345");
    http.get("http://localhost:12345");
});

上面是 Node.js 處理請求的過程,假設(shè)服務(wù)器啟動后,有兩個請求到來,處理過程如下。

  1. 第一次執(zhí)行回調(diào) handler,執(zhí)行 await timer.setTimeout(1000) 注冊一個定時器并退出執(zhí)行。
  2. 事件循環(huán)繼續(xù)處理下一個任務(wù)。
  3. 第二次執(zhí)行回調(diào) handler,執(zhí)行 await timer.setTimeout(1000) 注冊一個定時器并退出執(zhí)行。
  4. 事件循環(huán)繼續(xù)處理下一個任務(wù),發(fā)現(xiàn)沒有任務(wù)需要處理了,等待定時器超時。
  5. 第一個定時器超時,await 恢復(fù)執(zhí)行。
  6. 第二個定時器超時,await 恢復(fù)執(zhí)行。

通過同步方式寫異步代碼的方式,極大減少了代碼的編寫難度和提高了代碼的可維護(hù)性。但是因為 Node.js 是跑在單個線程的,所以這種以同步方式寫異步代碼的方式的意義也僅僅是代碼層面的。

Go

接下來看看 Go 的做法。

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
package main


import (
	"sync"
	"time"
)


func main() {
	var wg sync.WaitGroup
	wg.Add(2)
	for i := 0; i < 2; i++ {
		go func() {
			defer wg.Done()
			time.Sleep(time.Second)
		}()
	}
	wg.Wait()
}

上面的代碼中,在主協(xié)程中創(chuàng)建了兩個子協(xié)程,每個子協(xié)程睡眠 1s 后退出。從中可以看到在 Go 中多了一個協(xié)程的概念,并且天然是以同步方式執(zhí)行異步代碼的,在 Go 中,所有 API 都是同步執(zhí)行的,哪怕是一個異步操作。那么 Go 的同步實現(xiàn)異步代碼是怎么實現(xiàn)的呢?和 Node.js 的實現(xiàn)有什么區(qū)別?下面看一下 time.Sleep 的實現(xiàn)。

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
func timeSleep(ns int64) {
	gp := getg()
	t := gp.timer
	if t == nil {
		t = new(timer)
		gp.timer = t
	}
  // 超時回調(diào)
	t.f = goroutineReady
  // 當(dāng)前協(xié)程
	t.arg = gp
  // 絕對超時時間
	t.nextwhen = nanotime() + ns
  // 阻塞協(xié)程,并重新調(diào)度其他協(xié)程執(zhí)行
	gopark(...)
}

可以看到執(zhí)行 time.Sleep 時會導(dǎo)致整個協(xié)程阻塞并切換到其他就緒協(xié)程執(zhí)行。接著看一下超時后的邏輯。

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
func goroutineReady(arg any, seq uintptr) {
	goready(arg.(*g), 0)
}


func goready(gp *g, traceskip int) {
	systemstack(func() {
		ready(gp, traceskip, true)
	})
}


func ready(gp *g) {
  // 修改協(xié)程為就緒
	casgstatus(gp, _Gwaiting, _Grunnable)
  // 放入隊列等待調(diào)度執(zhí)行
	runqput(mp.p.ptr(), gp, next)
	wakep()
}

超時后 Go 會喚醒協(xié)程把它放入隊列等待執(zhí)行。

總結(jié)

從前面的介紹可以看到同步寫異步代碼都是通過暫停執(zhí)行和恢復(fù)執(zhí)行來實現(xiàn)的,Node.js 是通過 await 控制函數(shù)的暫停和繼續(xù)執(zhí)行,但是它不會影響執(zhí)行實體(線程)的狀態(tài),而 Go 是通過實現(xiàn)新的執(zhí)行實體協(xié)程并阻塞協(xié)程實現(xiàn)的。當(dāng)在單線程環(huán)境中,它們的區(qū)別不是很大,但是 Go 的底層是多線程的,也就是說所有的協(xié)程是分布在多個線程中執(zhí)行的,所以 Go 協(xié)程的意義不僅是實現(xiàn)了同步寫異步代碼,而且還可以利用多核,提高應(yīng)用的性能。另外 Node.js 是在單個線程中實現(xiàn)一個事件循環(huán),在事件循環(huán)中不斷處理就緒的任務(wù),而 Go 是在每個線程中都實現(xiàn)了一個事件循環(huán),這個事件循環(huán)就是調(diào)度器,調(diào)度器會不斷地選擇就緒的協(xié)程執(zhí)行,并判斷哪些定時器超時了,哪些網(wǎng)絡(luò) IO 就緒了,從而把對應(yīng)的協(xié)程加入到調(diào)度隊列中,如此反復(fù)。

責(zé)任編輯:姜華 來源: 編程雜技
相關(guān)推薦

2012-09-17 10:35:41

JavaScriptJS代碼

2023-11-27 08:00:36

開發(fā)數(shù)據(jù)庫查詢

2016-10-25 16:04:49

GeneratorPromiseJavaScript

2024-11-14 09:40:06

RPC框架NettyJava

2015-10-12 08:59:57

異步代碼測試

2020-09-25 18:10:06

Python 開發(fā)編程語言

2018-01-30 18:15:12

Python網(wǎng)絡(luò)爬蟲gevent

2022-06-13 06:20:42

setStatereact18

2023-03-13 17:18:09

OkHttp同步異步

2024-10-09 11:31:51

2023-09-07 08:15:58

場景同步異步

2024-07-11 16:49:43

同步通信異步通信通信

2025-05-06 07:45:00

JavaScript異步代碼異步編程

2024-04-24 10:57:54

Golang編程

2019-12-12 10:46:15

Kubernetes容器系統(tǒng)

2021-08-02 11:13:28

人工智能機(jī)器學(xué)習(xí)技術(shù)

2021-04-02 11:05:57

Python同步異步

2012-03-01 20:32:29

iOS

2021-03-11 11:32:40

Python同步異步

2012-07-27 10:02:39

C#
點贊
收藏

51CTO技術(shù)棧公眾號