<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                ## 一、Golang中除了加Mutex鎖以外還有哪些方式安全讀寫共享變量? > `Golang`中`Goroutine`可以通過`Channel`進行安全讀寫共享變量。 ## 二、無緩沖 Chan 的發送和接收是否同步? >ch := make(chan int) ? 無緩沖的channel由于沒有緩沖發送和接收需要同步。 >ch := make(chan int, 2) 有緩沖channel不要求發送和接收操作同步。 * channel無緩沖時,發送阻塞直到數據被接收,接收阻塞直到讀到數據。 * channel有緩沖時,當緩沖滿時發送阻塞,當緩沖空時接收阻塞。 實例: >`c1:=make(chan int)`??無緩沖 `c2:=make(chan int,1)`有緩沖 > c1<-1 無緩沖的 不僅僅是 向`c1`通道放`1`而是 一直要有別的協程`<-c1`接手了 這個參數,那么`c1<-1`才會繼續下去,要不然就一直阻塞著。 而`c2<-1`則不會阻塞,因為緩沖大小是`1`只有當 放第二個值的時候 第一個還沒被人拿走,這時候才會阻塞。 無緩沖的 就是一個送信人去你家門口送信 ,你不在家 他不走,你一定要接下信,他才會走。無緩沖保證信能到你手上 有緩沖的 就是一個送信人去你家仍到你家的信箱 轉身就走 ,除非你的信箱滿了 他必須等信箱空下來。有緩沖的 保證 信能進你家的郵箱 ## 三、go語言的并發機制以及它所使用的CSP并發模型 ## 四、Golang 中常用的并發模型 Golang 中常用的并發模型有三種: #### 1、通過channel通知實現并發控制 無緩沖的通道指的是通道的大小為0,也就是說,這種類型的通道在接收前沒有能力保存任何值,它要求發送 goroutine 和接收 goroutine 同時準備好,才可以完成發送和接收操作。 從上面無緩沖的通道定義來看,發送 goroutine 和接收 gouroutine 必須是同步的,同時準備后,如果沒有同時準備好的話,先執行的操作就會阻塞等待,直到另一個相對應的操作準備好為止。這種無緩沖的通道我們也稱之為同步通道。 ``` func main() { ch := make(chan struct{}) go func() { fmt.Println("start working") time.Sleep(time.Second * 1) ch <- struct{}{} }() <-ch fmt.Println("finished") } ``` 當主`goroutine`運行到`<-ch`接受`channel`的值的時候,如果該`channel`中沒有數據,就會一直阻塞等待,直到有值。 這樣就可以簡單實現并發控制 #### 2、通過`sync`包中的`WaitGroup`實現并發控制 Goroutine是異步執行的,有的時候為了防止在結束mian函數的時候結束掉Goroutine,所以需要同步等待,這個時候就需要用 WaitGroup了,在 sync 包中,提供了 WaitGroup ,它會等待它收集的所有 goroutine 任務全部完成。在WaitGroup里主要有三個 方法: * Add, 可以添加或減少 goroutine的數量。 * Done, 相當于Add(-1)。 * Wait, 執行后會堵塞主線程,直到WaitGroup 里的值減至0。 在主 goroutine 中 Add(delta int) 索要等待goroutine 的數量。 在每一個 goroutine 完成后 Done() 表示這一個goroutine 已經完成,當所有的 goroutine 都完成后,在主 goroutine 中 WaitGroup 返回。 ``` func main(){ var wg sync.WaitGroup var urls = []string{ "http://www.golang.org/", "http://www.google.com/", } for _, url := range urls { wg.Add(1) go func(url string) { defer wg.Done() http.Get(url) }(url) } wg.Wait() } ``` 在`Golang`官網中對于`WaitGroup`介紹是`A WaitGroup must not be copied after first use`,在`WaitGroup`第一次使用后,不能被拷貝 應用示例: ``` func main(){ wg := sync.WaitGroup{} for i := 0; i < 5; i++ { wg.Add(1) go func(wg sync.WaitGroup, i int) { fmt.Printf("i:%d", i) wg.Done() }(wg, i) } wg.Wait() fmt.Println("exit") } ``` 它提示所有的`goroutine`都已經睡眠了,出現了死鎖。這是因為`wg`給拷貝傳遞到了`goroutine`中,導致只有`Add`操作,其實 Done操作是在`wg`的副本執行的。 因此`Wait`就死鎖了。 這個第一個修改方式:將匿名函數中`wg`的傳入類型改為`*sync.WaitGroup`,這樣就能引用到正確的`WaitGroup`了。 這個第二個修改方式:將匿名函數中的`wg`的傳入參數去掉,因為`Go`支持閉包類型,在匿名函數中可以直接使用外面的`wg`變量 ## 3、 在`Go 1.7`以后引進的強大的`Context`上下文,實現并發控制 通常,在一些簡單場景下使用 channel 和 WaitGroup 已經足夠了,但是當面臨一些復雜多變的網絡并發場景下 channel 和 WaitGroup 顯得有些力不從心了。 比如一個網絡請求 Request,每個 Request 都需要開啟一個 goroutine 做一些事情,這些 goroutine 又可能會開啟其他的 goroutine,比如數據庫和RPC服務。 所以我們需要一種可以跟蹤 goroutine 的方案,才可以達到控制他們的目的,這就是Go語言為我們提供的 Context,稱之為上下文非常貼切,它就是goroutine 的上下文。 它是包括一個程序的運行環境、現場和快照等。每個程序要運行時,都需要知道當前程序的運行狀態,通常Go 將這些封裝在一個 Context 里,再將它傳給要執行的 goroutine 。 `context`包主要是用來處理多個`goroutine`之間共享數據,及多個`goroutine`的管理。 `context`包的核心是`struct Context`,接口聲明如下: ``` // A Context carries a deadline, cancelation signal, and request-scoped values // across API boundaries. Its methods are safe for simultaneous use by multiple // goroutines. type Context interface { // Done returns a channel that is closed when this `Context` is canceled // or times out. Done() <-chan struct{} // Err indicates why this Context was canceled, after the Done channel // is closed. Err() error // Deadline returns the time when this Context will be canceled, if any. Deadline() (deadline time.Time, ok bool) // Value returns the value associated with key or nil if none. Value(key interface{}) interface{} } ``` Done() 返回一個只能接受數據的channel類型,當該context關閉或者超時時間到了的時候,該channel就會有一個取消信號 Err() 在Done() 之后,返回context 取消的原因。 Deadline() 設置該context cancel的時間點 Value() 方法允許 Context 對象攜帶request作用域的數據,該數據必須是線程安全的。 Context 對象是線程安全的,你可以把一個 Context 對象傳遞給任意個數的 gorotuine,對它執行 取消 操作時,所有 goroutine 都會接收到取消信號。 一個 Context 不能擁有 Cancel 方法,同時我們也只能 Done channel 接收數據。 其中的原因是一致的:接收取消信號的函數和發送信號的函數通常不是一個。 典型的場景是:父操作為子操作操作啟動 goroutine,子操作也就不能取消父操作。 ## 五、JSON 標準庫對 nil slice 和 空 slice 的處理是一致的嗎? 首先`JSON`標準庫對`nil slice`和`空 slice`的處理是不一致 通常錯誤的用法,會報數組越界的錯誤,因為只是聲明了`slice`,卻沒有給實例化的對象。 ~~~go var slice []int slice[1] = 0 ~~~ 此時slice的值是nil,這種情況可以用于需要返回slice的函數,當函數出現異常的時候,保證函數依然會有nil的返回值。 empty slice 是指slice不為nil,但是slice沒有值,slice的底層的空間是空的,此時的定義如下: >slice := make([]int,0) >slice := []int{} 當我們查詢或者處理一個空的列表的時候,這非常有用,它會告訴我們返回的是一個列表,但是列表內沒有任何值。 總之,nil slice 和 empty slice是不同的東西,需要我們加以區分的。 ## 六、協程,線程,進程的區別 進程 進程是具有一定獨立功能的程序關于某個數據集合上的一次運行活動,進程是系統進行資源分配和調度的一個獨立單位。每個進程都有自己的獨立內存空間,不同進程通過進程間通信來通信。由于進程比較重量,占據獨立的內存,所以上下文進程間的切換開銷(棧、寄存器、虛擬內存、文件句柄等)比較大,但相對比較穩定安全。 線程 線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位.線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源。線程間通信主要通過共享內存,上下文切換很快,資源開銷較少,但相比進程不夠穩定容易丟失數據。 協程 協程是一種用戶態的輕量級線程,協程的調度完全由用戶控制。協程擁有自己的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復先前保存的寄存器上下文和棧,直接操作棧則基本沒有內核切換的開銷,可以不加鎖的訪問全局變量,所以上下文的切換非常快。 ## 七、互斥鎖,讀寫鎖,死鎖問題是怎么解決 互斥鎖 互斥鎖就是互斥變量mutex,用來鎖住臨界區的. 條件鎖就是條件變量,當進程的某些資源要求不滿足時就進入休眠,也就是鎖住了。當資源被分配到了,條件鎖打開,進程繼續運行;讀寫鎖,也類似,用于緩沖區等臨界資源能互斥訪問的。 讀寫鎖 通常有些公共數據修改的機會很少,但其讀的機會很多。并且在讀的過程中會伴隨著查找,給這種代碼加鎖會降低我們的程序效率。讀寫鎖可以解決這個問題。 ![](https://img.kancloud.cn/38/e5/38e533229caa79f0d832d8c1a57a8d27_291x121.png) 注意:寫獨占,讀共享,寫鎖優先級高 死鎖 一般情況下,如果同一個線程先后兩次調用lock,在第二次調用時,由于鎖已經被占用,該線程會掛起等待別的線程釋放鎖,然而鎖正是被自己占用著的,該線程又被掛起而沒有機會釋放鎖,因此就永遠處于掛起等待狀態了,這叫做死鎖(Deadlock)。 另外一種情況是:若線程A獲得了鎖1,線程B獲得了鎖2,這時線程A調用lock試圖獲得鎖2,結果是需要掛起等待線程B釋放鎖2,而這時線程B也調用lock試圖獲得鎖1,結果是需要掛起等待線程A釋放鎖1,于是線程A和B都永遠處于掛起狀態了。 死鎖產生的四個必要條件: * 互斥條件:一個資源每次只能被一個進程使用 * 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。 * 不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。 * 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關系。 這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會發生死鎖。 a. 預防死鎖 可以把資源一次性分配:(破壞請求和保持條件) 然后剝奪資源:即當某進程新的資源未滿足時,釋放已占有的資源(破壞不可剝奪條件) 資源有序分配法:系統給每類資源賦予一個編號,每一個進程按編號遞增的順序請求資源,釋放則相反(破壞環路等待條件) b. 避免死鎖 預防死鎖的幾種策略,會嚴重地損害系統性能。因此在避免死鎖時,要施加較弱的限制,從而獲得 較滿意的系統性能。由于在避免死鎖的策略中,允許進程動態地申請資源。因而,系統在進行資源分配之前預先計算資源分配的安全性。若此次分配不會導致系統進入不安全狀態,則將資源分配給進程;否則,進程等待。其中最具有代表性的避免死鎖算法是銀行家算法。 c. 檢測死鎖 首先為每個進程和每個資源指定一個唯一的號碼,然后建立資源分配表和進程等待表. d. 解除死鎖 當發現有進程死鎖后,便應立即把它從死鎖狀態中解脫出來,常采用的方法有. e. 剝奪資源 從其它進程剝奪足夠數量的資源給死鎖進程,以解除死鎖狀態. f. 撤消進程 可以直接撤消死鎖進程或撤消代價最小的進程,直至有足夠的資源可用,死鎖狀態.消除為止.所謂代價是指優先級、運行代價、進程的重要性和價值等。 ## 八、Data Race(數據競爭)問題怎么解決?能不能不加鎖解決這個問題? 同步訪問共享數據是處理數據競爭的一種有效的方法。golang在1.1之后引入了競爭檢測機制,可以使用 go run -race 或者 go build -race來進行靜態檢測。 其在內部的實現是,開啟多個協程執行同一個命令, 并且記錄下每個變量的狀態. 競爭檢測器基于C/C++的ThreadSanitizer 運行時庫,該庫在Google內部代碼基地和Chromium找到許多錯誤。這個技術在2012年九月集成到Go中,從那時開始,它已經在標準庫中檢測到42個競爭條件。現在,它已經是我們持續構建過程的一部分,當競爭條件出現時,它會繼續捕捉到這些錯誤。 競爭檢測器已經完全集成到Go工具鏈中,僅僅添加-race標志到命令行就使用了檢測器。 ``` go test -race mypkg // 測試包 go run -race mysrc.go // 編譯和運行程序 go build -race mycmd // 構建程序 go install -race mypkg // 安裝程序 ``` 要想解決數據競爭的問題可以使用互斥鎖sync.Mutex,解決數據競爭(Data race),也可以使用管道解決,使用管道的效率要比互斥鎖高。 ## 九、什么是channel,為什么它可以做到線程安全? Channel是Go中的一個核心類型,可以把它看成一個管道,通過它并發核心單元就可以發送或者接收數據進行通訊(communication),Channel也可以理解是一個先進先出的隊列,通過管道進行通信。 Golang的Channel,發送一個數據到Channel 和 從Channel接收一個數據 都是 原子性的。而且Go的設計思想就是:不要通過共享內存來通信,而是通過通信來共享內存,前者就是傳統的加鎖,后者就是Channel。也就是說,設計Channel的主要目的就是在多任務間傳遞數據的,這當然是安全的。 ## 十、Epoll原理 [推薦](https://blog.csdn.net/daaikuaichuan/article/details/83862311?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165353338916781685325199%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165353338916781685325199&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-83862311-null-null.142^v10^control,157^v12^new_style&utm_term=Epoll%E5%8E%9F%E7%90%86&spm=1018.2226.3001.4187) ## 十一、Golang GC(垃圾清除) 時會發生什么? 引入了語言層面的自動內存管理 – 也就是語言的使用者只用關注內存的申請而不必關心內存的釋放,內存釋放由虛擬機(virtual machine)或運行時(runtime)來自動進行管理。而這種對不再使用的內存資源進行自動回收的行為就被稱為垃圾回收。 常用的垃圾回收的方法: #### 1、引用計數(reference counting) 這是最簡單的一種垃圾回收算法,和之前提到的智能指針異曲同工。對每個對象維護一個引用計數,當引用該對象的對象被銷毀或更新時被引用對象的引用計數自動減一,當被引用對象被創建或被賦值給其他對象時引用計數自動加一。當引用計數為0時則立即回收對象。 這種方法的優點是實現簡單,并且內存的回收很及時。這種算法在內存比較緊張和實時性比較高的系統中使用的比較廣泛,如ios cocoa框架,php,python等。 但是簡單引用計數算法也有明顯的缺點: * 頻繁更新引用計數降低了性能。 一種簡單的解決方法就是編譯器將相鄰的引用計數更新操作合并到一次更新;還有一種方法是針對頻繁發生的臨時變量引用不進行計數,而是在引用達到0時通過掃描堆棧確認是否還有臨時對象引用而決定是否釋放。等等還有很多其他方法,具體可以參考這里。 * 循環引用。 當對象間發生循環引用時引用鏈中的對象都無法得到釋放。最明顯的解決辦法是避免產生循環引用,如cocoa引入了strong指針和weak指針兩種指針類型。或者系統檢測循環引用并主動打破循環鏈。當然這也增加了垃圾回收的復雜度。 #### 2、 標記-清除(mark and sweep) 標記-清除(mark and sweep)分為兩步,標記從根變量開始迭代得遍歷所有被引用的對象,對能夠通過應用遍歷訪問到的對象都進行標記為“被引用”;標記完成后進行清除操作,對沒有標記過的內存進行回收(回收同時可能伴有碎片整理操作)。這種方法解決了引用計數的不足,但是也有比較明顯的問題:每次啟動垃圾回收都會暫停當前所有的正常代碼執行,回收是系統響應能力大大降低!當然后續也出現了很多mark&sweep算法的變種(如三色標記法)優化了這個問題。 #### 3、分代搜集(generation) java的jvm 就使用的分代回收的思路。在面向對象編程語言中,絕大多數對象的生命周期都非常短。分代收集的基本思想是,將堆劃分為兩個或多個稱為代(generation)的空間。新創建的對象存放在稱為新生代(young generation)中(一般來說,新生代的大小會比 老年代小很多),隨著垃圾回收的重復執行,生命周期較長的對象會被提升(promotion)到老年代中(這里用到了一個分類的思路,這個是也是科學思考的一個基本思路)。 因此,新生代垃圾回收和老年代垃圾回收兩種不同的垃圾回收方式應運而生,分別用于對各自空間中的對象執行垃圾回收。新生代垃圾回收的速度非常快,比老年代快幾個數量級,即使新生代垃圾回收的頻率更高,執行效率也仍然比老年代垃圾回收強,這是因為大多數對象的生命周期都很短,根本無需提升到老年代。 Golang GC 時會發生什么? Golang 1.5后,采取的是“非分代的、非移動的、并發的、三色的”標記清除垃圾回收算法。 golang 中的 gc 基本上是標記清除的過程: ![](https://img.kancloud.cn/b6/5c/b65c05a87fe19e388c7a5e248c4e6d65_2030x930.png) gc的過程一共分為四個階段: 1. 棧掃描(開始時STW) 2. 第一次標記(并發) 3. 第二次標記(STW) 4. 清除(并發) 整個進程空間里申請每個對象占據的內存可以視為一個圖,初始狀態下每個內存對象都是白色標記。 1. 先STW,做一些準備工作,比如 enable write barrier。然后取消STW,將掃描任務作為多個并發的goroutine立即入隊給調度器,進而被CPU處理。 2. 第一輪先掃描root對象,包括全局指針和 goroutine 棧上的指針,標記為灰色放入隊列。 3. 第二輪將第一步隊列中的對象引用的對象置為灰色加入隊列,一個對象引用的所有對象都置灰并加入隊列后,這個對象才能置為黑色并從隊列之中取出。循環往復,最后隊列為空時,整個圖剩下的白色內存空間即不可到達的對象,即沒有被引用的對象; 4. 第三輪再次STW,將第二輪過程中新增對象申請的內存進行標記(灰色),這里使用了write barrier(寫屏障)去記錄Golang gc 優化的核心就是盡量使得 STW(Stop The World) 的時間越來越短。 ## 十二、并發編程概念是什么? 并行是指兩個或者多個事件在同一時刻發生;并發是指兩個或多個事件在同一時間間隔發生。 并行是在不同實體上的多個事件,并發是在同一實體上的多個事件。在一臺處理器上“同時”處理多個任務,在多臺處理器上同時處理多個任務。如hadoop分布式集群 并發偏重于多個任務交替執行,而多個任務之間有可能還是串行的。而并行是真正意義上的“同時執行”。 并發編程是指在一臺處理器上“同時”處理多個任務。并發是在同一實體上的多個事件。多個事件在同一時間間隔發生。并發編程的目標是充分的利用處理器的每一個核,以達到最高的處理性能。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看