最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一文帶你深入理解Golang中的RWMutex

 更新時(shí)間:2023年04月03日 08:30:16   作者:eleven26  
這篇文章主要為大家詳細(xì)介紹了Golang中RWMutex的相關(guān)知識(shí),知其然,更要知其所以然。文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下

在上一篇文章《深入理解 go Mutex》中, 我們已經(jīng)對(duì) go Mutex 的實(shí)現(xiàn)原理有了一個(gè)大致的了解,也知道了 Mutex 可以實(shí)現(xiàn)并發(fā)讀寫的安全。 今天,我們?cè)賮砜纯戳硗庖环N鎖,RWMutex,有時(shí)候,其實(shí)我們讀數(shù)據(jù)的頻率要遠(yuǎn)遠(yuǎn)高于寫數(shù)據(jù)的頻率, 而且不同協(xié)程應(yīng)該可以同時(shí)讀取的,這個(gè)時(shí)候,RWMutex 就派上用場(chǎng)了。

RWMutex 的實(shí)現(xiàn)原理和 Mutex 類似,只是在 Mutex 的基礎(chǔ)上,區(qū)分了讀鎖和寫鎖:

  • 讀鎖:只要沒有寫鎖,就可以獲取讀鎖,多個(gè)協(xié)程可以同時(shí)獲取讀鎖(可以并行讀)。
  • 寫鎖:只能有一個(gè)協(xié)程獲取寫鎖,其他協(xié)程想獲取讀鎖或?qū)戞i都只能等待。

下面就讓我們來深入了解一下 RWMutex 的基本使用和實(shí)現(xiàn)原理等內(nèi)容。

RWMutex 的整體模型

正如 RWMutex 的命名那樣,它是區(qū)分了讀鎖和寫鎖的鎖,所以我們可以從讀和寫兩個(gè)方面來看 RWMutex 的模型。

下文中的 reader 指的是進(jìn)行讀操作的 goroutine,writer 指的是進(jìn)行寫操作的 goroutine。

讀操作模型

我們可以用下圖來表示 RWMutex 的讀操作模型:

上圖使用了 w.Lock,是因?yàn)?RWMutex 的實(shí)現(xiàn)中,寫鎖是使用 Mutex 來實(shí)現(xiàn)的。

說明:

  • 讀操作的時(shí)候可以同時(shí)有多個(gè) goroutine 持有 RLock,然后進(jìn)入臨界區(qū)。(也就是可以并行讀),上圖的 G1、G2G3 就是同時(shí)持有 RLock 的幾個(gè) goroutine。
  • 在讀操作的時(shí)候,如果有 goroutine 持有 RLock,那么其他 goroutine (不管是讀還是寫)就只能等待,直到所有持有 RLock 的 goroutine 釋放鎖。
  • 也就是上圖的 G4 需要等待 G1、G2G3 釋放鎖之后才能進(jìn)入臨界區(qū)。
  • 最后,因?yàn)?G5G6 這兩個(gè)協(xié)程獲取鎖的時(shí)機(jī)比 G4 晚,所以它們會(huì)在 G4 釋放鎖之后才能進(jìn)入臨界區(qū)。

寫操作模型

我們可以用下圖來表示 RWMutex 的寫操作模型:

說明:

寫操作的時(shí)候只能有一個(gè) goroutine 持有 Lock,然后進(jìn)入臨界區(qū),釋放寫鎖之前,所有其他的 goroutine 都只能等待。

上圖的 G1~G5 表示的是按時(shí)間順序先后獲取鎖的幾個(gè) goroutine。

上面幾個(gè) goroutine 獲取鎖的過程是:

  • G1 獲取寫鎖,進(jìn)入臨界區(qū)。然后 G2、G3G4G5 都在等待。
  • G1 釋放寫鎖之后,G2G3 可以同時(shí)獲取讀鎖,進(jìn)入臨界區(qū)。然后 G3、G4G5 都在等待。
  • G2G3 可以同時(shí)獲取讀鎖,進(jìn)入臨界區(qū)。然后 G4G5 都在等待。
  • G2G3 釋放讀鎖之后,G4 獲取寫鎖,進(jìn)入臨界區(qū)。然后 G5 在等待。
  • 最后,G4 釋放寫鎖,G5 獲取讀鎖,進(jìn)入臨界區(qū)。

基本用法

RWMutex 中包含了以下的方法:

  • Lock:獲取寫鎖,如果有其他 goroutine 持有讀鎖或?qū)戞i,那么就會(huì)阻塞等待。
  • Unlock:釋放寫鎖。
  • RLock:獲取讀鎖,如果有其他 goroutine 持有寫鎖,那么就會(huì)阻塞等待。
  • RUnlock:釋放讀鎖。

其他不常用的方法:

  • RLocker:返回一個(gè)讀鎖,該鎖包含了 RLockRUnlock 方法,可以用來獲取讀鎖和釋放讀鎖。
  • TryLock: 嘗試獲取寫鎖,如果獲取成功,返回 true,否則返回 false。不會(huì)阻塞等待。
  • TryRLock: 嘗試獲取讀鎖,如果獲取成功,返回 true,否則返回 false。不會(huì)阻塞等待。

一個(gè)簡(jiǎn)單的例子

我們可以通過下面的例子來看一下 RWMutex 的基本用法:

package mutex

import (
   "sync"
   "testing"
)

var config map[string]string
var mu sync.RWMutex

func TestRWMutex(t *testing.T) {
   config = make(map[string]string)

   // 啟動(dòng) 10 個(gè) goroutine 來寫
   var wg1 sync.WaitGroup
   wg1.Add(10)
   for i := 0; i < 10; i++ {
      go func() {
         set("foo", "bar")
         wg1.Done()
      }()
   }

   // 啟動(dòng) 100 個(gè) goroutine 來讀
   var wg2 sync.WaitGroup
   wg2.Add(100)
   for i := 0; i < 100; i++ {
      go func() {
         get("foo")
         wg2.Done()
      }()
   }

   wg1.Wait()
   wg2.Wait()
}

// 獲取配置
func get(key string) string {
   // 獲取讀鎖,可以多個(gè) goroutine 并發(fā)讀取
   mu.RLock()
   defer mu.RUnlock()

   if v, ok := config[key]; ok {
      return v
   }

   return ""
}

// 設(shè)置配置
func set(key, val string) {
   // 獲取寫鎖
   mu.Lock()
   defer mu.Unlock()

   config[key] = val
}

上面的例子中,我們啟動(dòng)了 10 個(gè) goroutine 來寫配置,啟動(dòng)了 100 個(gè) goroutine 來讀配置。 這跟我們現(xiàn)實(shí)開發(fā)中的場(chǎng)景是一樣的,很多時(shí)候其實(shí)是讀多寫少的。 如果我們?cè)谧x的時(shí)候也使用互斥鎖,那么就會(huì)導(dǎo)致讀的性能非常差,因?yàn)樽x操作一般都不會(huì)有副作用的,但是如果使用互斥鎖,那么就只能一個(gè)一個(gè)的讀了。

而如果我們使用 RWMutex,那么就可以同時(shí)有多個(gè) goroutine 來讀取配置,這樣就可以大大提高讀的性能。 因?yàn)槲覀冞M(jìn)行讀操作的時(shí)候,可以多個(gè) goroutine 并發(fā)讀取,這樣就可以大大提高讀的性能。

RWMutex 使用的注意事項(xiàng)

《深入理解 go Mutex》中,我們已經(jīng)講過了 Mutex 的使用注意事項(xiàng), 其實(shí) RWMutex 的使用注意事項(xiàng)也是差不多的:

  • 不要忘記釋放鎖,不管是讀鎖還是寫鎖。
  • Lock 之后,沒有釋放鎖之前,不能再次使用 Lock
  • Unlock 之前,必須已經(jīng)調(diào)用了 Lock,否則會(huì) panic
  • 在第一次使用 RWMutex 之后,不能復(fù)制,因?yàn)檫@樣一來 RWMutex 的狀態(tài)也會(huì)被復(fù)制。這個(gè)可以使用 go vet 來檢查。

源碼剖析

RWMutex 的一些實(shí)現(xiàn)原理跟 Mutex 是一樣的,比如阻塞的時(shí)候使用信號(hào)量等,在 Mutex 那一篇中已經(jīng)有講解了,這里不再贅述。 這里就 RWMutex 的實(shí)現(xiàn)原理進(jìn)行一些簡(jiǎn)單的剖析。

RWMutex 結(jié)構(gòu)體

RWMutex 的結(jié)構(gòu)體定義如下:

type RWMutex struct {
   w           Mutex        // 互斥鎖,用于保護(hù)讀寫鎖的狀態(tài)
   writerSem   uint32       // writer 信號(hào)量
   readerSem   uint32       // reader 信號(hào)量
   readerCount atomic.Int32 // 所有 reader 數(shù)量
   readerWait  atomic.Int32 // writer 等待完成的 reader 數(shù)量
}

各字段含義:

  • w:互斥鎖,用于保護(hù)讀寫鎖的狀態(tài)。RWMutex 的寫鎖是互斥鎖,所以直接使用 Mutex 就可以了。
  • writerSem:writer 信號(hào)量,用于實(shí)現(xiàn)寫鎖的阻塞等待。
  • readerSem:reader 信號(hào)量,用于實(shí)現(xiàn)讀鎖的阻塞等待。
  • readerCount:所有 reader 數(shù)量(包括已經(jīng)獲取讀鎖的和正在等待獲取讀鎖的 reader)。
  • readerWait:writer 等待完成的 reader 數(shù)量(也就是獲取寫鎖的時(shí)刻,已經(jīng)獲取到讀鎖的 reader 數(shù)量)。

因?yàn)橐獏^(qū)分讀鎖和寫鎖,所以在 RWMutex 中,我們需要兩個(gè)信號(hào)量,一個(gè)用于實(shí)現(xiàn)寫鎖的阻塞等待,一個(gè)用于實(shí)現(xiàn)讀鎖的阻塞等待。 我們需要特別注意的是 readerCountreaderWait 這兩個(gè)字段,我們可能會(huì)比較好奇,為什么有了 readerCount 這個(gè)字段, 還需要 readerWait 這個(gè)字段呢?

這是因?yàn)?,我們?cè)趪L試獲取寫鎖的時(shí)候,可能會(huì)有多個(gè) reader 正在使用讀鎖,這時(shí)候我們需要知道有多少個(gè) reader 正在使用讀鎖, 等待這些 reader 釋放讀鎖之后,就獲取寫鎖了,而 readerWait 這個(gè)字段就是用來記錄這個(gè)數(shù)量的。 在 Lock 中獲取寫鎖的時(shí)候,如果觀測(cè)到 readerWait 不為 0 則會(huì)阻塞等待,直到 readerWait 為 0 之后才會(huì)真正獲取寫鎖,然后才可以進(jìn)行寫操作。

讀鎖源碼剖析

獲取讀鎖的方法如下:

// 獲取讀鎖
func (rw *RWMutex) RLock() {
   if rw.readerCount.Add(1) < 0 {
      // 有 writer 在使用鎖,阻塞等待 writer 完成
      runtime_SemacquireRWMutexR(&rw.readerSem, false, 0)
   }
}

讀鎖的實(shí)現(xiàn)很簡(jiǎn)單,先將 readerCount 加 1,如果加 1 之后的值小于 0,說明有 writer 正在使用鎖,那么就需要阻塞等待 writer 完成。

釋放讀鎖的方法如下:

// 釋放讀鎖
func (rw *RWMutex) RUnlock() {
   // readerCount 減 1,如果 readerCount 小于 0 說明有 writer 在等待
   if r := rw.readerCount.Add(-1); r < 0 {
      // 有 writer 在等待,喚醒 writer
      rw.rUnlockSlow(r)
   }
}

// 喚醒 writer
func (rw *RWMutex) rUnlockSlow(r int32) {
   // 未 Lock 就 Unlock,panic
   if r+1 == 0 || r+1 == -rwmutexMaxReaders {
      fatal("sync: RUnlock of unlocked RWMutex")
   }
   // readerWait 減 1,返回值是新的 readerWait 值
   if rw.readerWait.Add(-1) == 0 {
      // 最后一個(gè) reader 喚醒 writer
      runtime_Semrelease(&rw.writerSem, false, 1)
   }
}

讀鎖的實(shí)現(xiàn)總結(jié):

  • 獲取讀鎖的時(shí)候,會(huì)將 readerCount 加 1
  • 如果正在獲取讀鎖的時(shí)候,發(fā)現(xiàn) readerCount 小于 0,說明有 writer 正在使用鎖,那么就需要阻塞等待 writer 完成。
  • 釋放讀鎖的時(shí)候,會(huì)將 readerCount 減 1
  • 如果 readerCount 減 1 之后小于 0,說明有 writer 正在等待,那么就需要喚醒 writer。
  • 喚醒 writer 的時(shí)候,會(huì)將 readerWait 減 1,如果 readerWait 減 1 之后為 0,說明 writer 獲取鎖的時(shí)候存在的 reader 都已經(jīng)釋放了讀鎖,可以獲取寫鎖了。

·rwmutexMaxReaders算是一個(gè)特殊的標(biāo)識(shí),在獲取寫鎖的時(shí)候會(huì)將readerCount的值減去rwmutexMaxReaders, 所以在其他地方可以根據(jù) readerCount` 是否小于 0 來判斷是否有 writer 正在使用鎖。

寫鎖源碼剖析

獲取寫鎖的方法如下:

// 獲取寫鎖
func (rw *RWMutex) Lock() {
   // 首先,解決與其他寫入者的競(jìng)爭(zhēng)。
   rw.w.Lock()
   // 向讀者宣布有一個(gè)待處理的寫入。
   // r 就是當(dāng)前還沒有完成的讀操作,等這部分讀操作完成之后才可以獲取寫鎖。
   r := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders
   // 等待活躍的 reader
   if r != 0 && rw.readerWait.Add(r) != 0 {
      // 阻塞,等待最后一個(gè) reader 喚醒
      runtime_SemacquireRWMutex(&rw.writerSem, false, 0)
   }
}

釋放寫鎖的方法如下:

// 釋放寫鎖
func (rw *RWMutex) Unlock() {
   // 向 readers 宣布沒有活動(dòng)的 writer。
   r := rw.readerCount.Add(rwmutexMaxReaders)
   if r >= rwmutexMaxReaders { // r >= 0 并且 < rwmutexMaxReaders 才是正常的(r 是持有寫鎖期間嘗試獲取讀鎖的 reader 數(shù)量)
      fatal("sync: Unlock of unlocked RWMutex")
   }
   // 如果有 reader 在等待寫鎖釋放,那么喚醒這些 reader。
   for i := 0; i < int(r); i++ {
      runtime_Semrelease(&rw.readerSem, false, 0)
   }
   // 允許其他的 writer 繼續(xù)進(jìn)行。
   rw.w.Unlock()
}

寫鎖的實(shí)現(xiàn)總結(jié):

  • 獲取寫鎖的時(shí)候,會(huì)將 readerCount 減去 rwmutexMaxReaders,這樣就可以區(qū)分讀鎖和寫鎖了。
  • 如果 readerCount 減去 rwmutexMaxReaders 之后不為 0,說明有 reader 正在使用讀鎖,那么就需要阻塞等待這些 reader 釋放讀鎖。
  • 釋放寫鎖的時(shí)候,會(huì)將 readerCount 加上 rwmutexMaxReaders。
  • 如果 readerCount 加上 rwmutexMaxReaders 之后大于 0,說明有 reader 正在等待寫鎖釋放,那么就需要喚醒這些 reader。

TryRLock 和 TryLock

TryRLockTryLock 的實(shí)現(xiàn)都很簡(jiǎn)單,都是嘗試獲取讀鎖或者寫鎖,如果獲取不到就返回 false,獲取到了就返回 true,這兩個(gè)方法不會(huì)阻塞等待。

// TryRLock 嘗試鎖定 rw 以進(jìn)行讀取,并報(bào)告是否成功。
func (rw *RWMutex) TryRLock() bool {
   for {
      c := rw.readerCount.Load()
      // 有 goroutine 持有寫鎖
      if c < 0 {
         return false
      }
      // 嘗試獲取讀鎖
      if rw.readerCount.CompareAndSwap(c, c+1) {
         return true
      }
   }
}

// TryLock 嘗試鎖定 rw 以進(jìn)行寫入,并報(bào)告是否成功。
func (rw *RWMutex) TryLock() bool {
   // 寫鎖被占用
   if !rw.w.TryLock() {
      return false
   }
   // 讀鎖被占用
   if !rw.readerCount.CompareAndSwap(0, -rwmutexMaxReaders) {
      // 釋放寫鎖
      rw.w.Unlock()
      return false
   }
   // 成功獲取到鎖
   return true
}

總結(jié)

RWMutex 使用起來比較簡(jiǎn)單,相比 Mutex 而言,它區(qū)分了讀鎖和寫鎖,可以提高并發(fā)性能。最后,總結(jié)一下本文內(nèi)容:

RWMutex 有兩種鎖:讀鎖和寫鎖。

讀鎖可以被多個(gè) goroutine 同時(shí)持有,寫鎖只能被一個(gè) goroutine 持有。也就是可以并發(fā)讀,但只能互斥寫。

寫鎖被占用的時(shí)候,其他的讀和寫操作都會(huì)被阻塞。讀鎖被占用的時(shí)候,其他的寫操作會(huì)被阻塞,但是讀操作不會(huì)被阻塞。除非讀操作發(fā)生在一個(gè)新的寫操作之后。

RWMutex 包含以下幾個(gè)方法:

  • Lock:獲取寫鎖,如果有其他的寫鎖或者讀鎖被占用,那么就會(huì)阻塞等待。
  • Unlock:釋放寫鎖。
  • RLock:獲取讀鎖,如果寫鎖被占用,那么就會(huì)阻塞等待。
  • RUnlock:釋放讀鎖。

也包含了兩個(gè)非阻塞的方法:

  • TryLock:嘗試獲取寫鎖,如果獲取不到就返回 false,獲取到了就返回 true。
  • TryRLock:嘗試獲取讀鎖,如果獲取不到就返回 false,獲取到了就返回 true。

RWMutex 使用的注意事項(xiàng)跟 Mutex 差不多:

  • 使用之后不能復(fù)制
  • Unlock 之前需要有 Lock 調(diào)用,否則 panic,RUnlock 之前需要有 RLock 調(diào)用,否則 panic。
  • 不要忘記使用 UnlockRUnlock 釋放鎖。

RWMutex 的實(shí)現(xiàn):

  • 寫鎖還是使用 Mutex 來實(shí)現(xiàn)。
  • 獲取讀鎖和寫鎖的時(shí)候,如果獲取不到都會(huì)阻塞等待,直到被喚醒。
  • 獲取寫鎖的時(shí)候,會(huì)將 readerCount 減去 rwmutexMaxReaders,這樣就可以直到有寫鎖被占用。釋放寫鎖的時(shí)候,會(huì)將 readerCount 加上 rwmutexMaxReaders。
  • 獲取寫鎖的時(shí)候,如果還有讀操作未完成,那么這一次獲取寫鎖只會(huì)等待這部分未完成的讀操作完成。所有后續(xù)的操作只能等待這一次寫鎖釋放。

以上就是一文帶你深入理解Golang中的RWMutex的詳細(xì)內(nèi)容,更多關(guān)于Golang RWMutex的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Golang 字符串轉(zhuǎn)time類型實(shí)現(xiàn)

    Golang 字符串轉(zhuǎn)time類型實(shí)現(xiàn)

    本文主要介紹了Golang 字符串轉(zhuǎn)time類型實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • Golang實(shí)現(xiàn)帶優(yōu)先級(jí)的select

    Golang實(shí)現(xiàn)帶優(yōu)先級(jí)的select

    這篇文章主要為大家詳細(xì)介紹了如何在Golang中實(shí)現(xiàn)帶優(yōu)先級(jí)的select,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Golang有一定的幫助,需要的可以參考一下
    2023-04-04
  • Go channel發(fā)送方和接收方如何相互阻塞等待源碼解讀

    Go channel發(fā)送方和接收方如何相互阻塞等待源碼解讀

    這篇文章主要為大家介紹了Go channel發(fā)送方和接收方如何相互阻塞等待源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • golang通過context控制并發(fā)的應(yīng)用場(chǎng)景實(shí)現(xiàn)

    golang通過context控制并發(fā)的應(yīng)用場(chǎng)景實(shí)現(xiàn)

    這篇文章主要介紹了golang通過context控制并發(fā)的應(yīng)用場(chǎng)景實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-01-01
  • 對(duì)Go語言中的context包源碼分析

    對(duì)Go語言中的context包源碼分析

    這篇文章主要對(duì)Go語言中的context包源碼進(jìn)行分析,context包析是1.15,context包定義了一個(gè)Context類型過這個(gè)Context接口類型,?就可以跨api邊界/跨進(jìn)程傳遞一些值,下面我們就來對(duì)context包源碼進(jìn)行分析,需要的小伙伴可以參考一下
    2022-02-02
  • Golang性能提升利器之SectionReader的用法詳解

    Golang性能提升利器之SectionReader的用法詳解

    本文將介紹 Go 語言中的 SectionReader,包括 SectionReader的基本使用方法、實(shí)現(xiàn)原理、使用注意事項(xiàng),感興趣的小伙伴可以了解一下
    2023-07-07
  • Go結(jié)合JavaScript實(shí)現(xiàn)抓取網(wǎng)頁中的圖像鏈接

    Go結(jié)合JavaScript實(shí)現(xiàn)抓取網(wǎng)頁中的圖像鏈接

    這篇文章主要為大家詳細(xì)介紹了Go語言如何結(jié)合JavaScript實(shí)現(xiàn)抓取網(wǎng)頁中的圖像鏈接,文中的示例代碼講解詳細(xì),有需要的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-11-11
  • golang使用bcrypt包對(duì)密碼進(jìn)行加密的方法實(shí)現(xiàn)

    golang使用bcrypt包對(duì)密碼進(jìn)行加密的方法實(shí)現(xiàn)

    本文主要介紹了golang使用bcrypt包對(duì)密碼進(jìn)行加密的方法實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • golang中map增刪改查的示例代碼

    golang中map增刪改查的示例代碼

    在Go語言中,map是一種內(nèi)置的數(shù)據(jù)結(jié)構(gòu),用于存儲(chǔ)鍵值對(duì),本文主要介紹了golang中map增刪改查的示例代碼,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-11-11
  • GO語言實(shí)現(xiàn)文件上傳代碼分享

    GO語言實(shí)現(xiàn)文件上傳代碼分享

    本文給大家分享的是一則使用golang實(shí)現(xiàn)文件上傳的代碼,主要是使用os.Create創(chuàng)建文件,io.Copy來保存文件,思路非常清晰,這里推薦給大家,有需要的小伙伴參考下吧。
    2015-03-03

最新評(píng)論

永安市| 棋牌| 兖州市| 黄骅市| 安乡县| 泰顺县| 东宁县| 施秉县| 故城县| 清河县| 内江市| 盐边县| 吕梁市| 新余市| 新龙县| 邹城市| 元阳县| 西华县| 嘉祥县| 白沙| 仁布县| 壶关县| 琼结县| 弥勒县| 湄潭县| 普兰县| 吉林市| 达尔| 祁连县| 元谋县| 垣曲县| 无棣县| 夏河县| 高唐县| 镇远县| 闽侯县| 海口市| 驻马店市| 佛冈县| 通榆县| 三原县|