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

Golang?sync.Map底層實(shí)現(xiàn)場(chǎng)景示例詳解

 更新時(shí)間:2023年09月07日 09:50:04   作者:EricLee  
這篇文章主要為大家介紹了Golang?sync.Map底層實(shí)現(xiàn)及使用場(chǎng)景示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

Go中普通的map是非線程安全的,想要線程安全的訪問(wèn)一個(gè)map,有兩種方式一種是map+mutex另一種就是原生的sync.Map,這篇文章會(huì)詳細(xì)的介紹sync.Map底層是如何實(shí)現(xiàn)的,以及一些常用的場(chǎng)景。

如何保證線程安全?

sync.Map的數(shù)據(jù)結(jié)構(gòu)如下:

type Map struct {
   mu Mutex // 鎖,保證寫操作及dirty晉升為read的線程安全
   read atomic.Value // readOnly 只讀map
   dirty map[any]*entry // 臟map,當(dāng)內(nèi)部有數(shù)據(jù)時(shí)就一定包含read中的數(shù)據(jù)
   misses int // read未命中次數(shù),當(dāng)達(dá)到一定次數(shù)時(shí)會(huì)觸發(fā)dirty中的護(hù)具晉升到read
}

如果只看這個(gè)結(jié)構(gòu)我們可能會(huì)有以下幾個(gè)疑問(wèn):

  • sync.Map中也用了mutex那和map+mutex的實(shí)現(xiàn)方式不就一樣了嗎?
  • misses做什么用的?
  • read的類型是一個(gè)atomic.Value而dirty是map[any]*entry,為什么不同?

sync.Map中也用了mutex那和map+mutex的實(shí)現(xiàn)方式不就一樣了嗎?

  • 在本質(zhì)上都是通過(guò)map+mutex的實(shí)現(xiàn)方式來(lái)實(shí)現(xiàn)的
  • sync.Map通過(guò)增加read map,降低在進(jìn)行讀取操作時(shí)的加鎖概率,增加讀取的性能。

misses做什么用的?

  • misses是用于標(biāo)記read中未命中次數(shù)的
  • 當(dāng)misses達(dá)到一定值時(shí)會(huì)觸發(fā)dirty的晉升(晉升為read)

具體源碼如下:

// 當(dāng)執(zhí)行Load操作時(shí)沒(méi)能在read中命中key,則進(jìn)行一次miss記錄
func (m *Map) missLocked() {
   // 1.miss計(jì)數(shù)加1
   m.misses++
   // 2.判斷dirty是否滿足晉升條件
   if m.misses < len(m.dirty) {
      // 2.1不滿足直接返回
      return
   }
   // 3.將dirty中的數(shù)據(jù)轉(zhuǎn)存到read的m中,舊的read中的數(shù)據(jù)被拋棄
   m.read.Store(readOnly{m: m.dirty})
   // 4.清空dirty
   m.dirty = nil
   // 5.重置miss計(jì)數(shù)
   m.misses = 0
}

從代碼中可以看到:

  • 當(dāng)misses值大于等于dirty中數(shù)據(jù)個(gè)數(shù)的時(shí)候會(huì)觸發(fā)dirty的晉升
  • 在dirty晉升時(shí),直直接把read重置成了一個(gè)新生成的readOnly,其中m為新的dirty,amended為默認(rèn)值false,保證每次觸發(fā)晉升都自動(dòng)將amended設(shè)置為了false
  • 在dirty晉升時(shí),并沒(méi)有觸發(fā)數(shù)據(jù)的拷貝

read的類型是一個(gè)atomic.Value而dirty是map[any]*entry,為什么不同?

type readOnly struct {
   m       map[any]*entry // read map中的數(shù)據(jù)
   amended bool // 標(biāo)記dirty map中是否有read中沒(méi)有的key,如果有,則此值為true
}
type entry struct {
   p unsafe.Pointer // *interface{}  一個(gè)指向具體數(shù)據(jù)的指針
}
  • read的類型底層是存儲(chǔ)的readOnly類型,而readOnly類型只是在map[any]*entry的基礎(chǔ)上增加了一個(gè)amended標(biāo)記
  • 如果amended為false,則代表dirty中沒(méi)有read中沒(méi)有的數(shù)據(jù),此時(shí)可以避免一次dirty操作(會(huì)加鎖),從而降低無(wú)意義的加鎖。
  • read被聲明為atomic.Value類型是為了滿足在無(wú)鎖的情況下多個(gè)Goroutine同時(shí)讀取read時(shí)的數(shù)據(jù)一致性

sync.Map適用于那些場(chǎng)景?

sync.Map更適合讀多寫少的場(chǎng)景,而當(dāng)map需要頻繁寫入的時(shí)候,map+mutex的方案通過(guò)控制鎖的力度可以達(dá)到比sync.Map更好的性能。

sync.Map不支持遍歷操作,因?yàn)樽x寫分離的設(shè)計(jì)使得在遍歷過(guò)程中可能存在一些未完成的修改操作,導(dǎo)致遍歷結(jié)果不確定。

為什么sync.Map適合讀多寫少的場(chǎng)景?

sync.Map的讀取方法為Load方法,具體的源碼實(shí)現(xiàn)如下:

func (m *Map) Load(key any) (value any, ok bool) {
   // 1.將read中的數(shù)據(jù)強(qiáng)轉(zhuǎn)為readOnly
   read, _ := m.read.Load().(readOnly)
   // 2.從read中查詢key,檢查數(shù)據(jù)是否存在
   e, ok := read.m[key]
   // 3.如果read中不存在,且amended標(biāo)記顯示dirty中存在read中沒(méi)有的key,則去dirty中查詢
   if !ok && read.amended {
      // 3.1開始操作dirty,需要加鎖保證線程安全
      m.mu.Lock()
      // 3.2 重新從read中檢查一次,避免在Lock執(zhí)行前dirty中的數(shù)據(jù)觸發(fā)了晉升到read的操作
      read, _ = m.read.Load().(readOnly)
      e, ok = read.m[key]
      // 3.3 同3
      if !ok && read.amended {
         // 3.4 從dirty中查詢
         e, ok = m.dirty[key]
         // 3.5 無(wú)論是否從dirty中查詢到數(shù)據(jù),都相當(dāng)于從read中miss了,需要更新miss計(jì)數(shù)(更新計(jì)數(shù)可能會(huì)觸發(fā)dirty數(shù)據(jù)的晉升)
         m.missLocked()
      }
      // 3.5 操作完成解鎖
      m.mu.Unlock()
   }
   // 4.檢測(cè)結(jié)果,ok為false代表沒(méi)有查詢到數(shù)據(jù)
   // ok為true分為兩種情況:1.從read中查詢到了數(shù)據(jù),read命中;2.從dirty中查詢到了數(shù)據(jù)
   // ok為false分為兩種情況:
   //    1.read沒(méi)有命中,但read.amended為false
   //    2.read沒(méi)有命中,read.amended為true,但dirty中也不存在
   if !ok {
      return nil, false
   }
   // 返回查詢到的數(shù)據(jù)(這個(gè)值也并不一定是真的存在,需要根據(jù)p確定是一個(gè)正常的值還是一個(gè)nil)
   return e.load()
}
// 當(dāng)從read或者dirty中獲取到一個(gè)key的值的指針時(shí),需要去加載對(duì)應(yīng)指針的值
func (e *entry) load() (value any, ok bool) {    
   // 1.院子操作獲取對(duì)應(yīng)地址的值
   p := atomic.LoadPointer(&e.p)
   // 2.如果值已經(jīng)不存在或者標(biāo)記為被刪除則返回nil,false
   if p == nil || p == expunged {
      return nil, false
   }
   // 3.返回具體的值,true
   return *(*any)(p), true
}

每次讀取數(shù)據(jù)時(shí),優(yōu)先從read中讀取,且read中數(shù)據(jù)的讀取不需要進(jìn)行加鎖操作;當(dāng)read中未命中且amended標(biāo)記顯示dirty中存在read中沒(méi)有的數(shù)據(jù)時(shí),才進(jìn)行dirty查詢,并加鎖。在讀多寫少的情況下,大多數(shù)時(shí)候數(shù)據(jù)都在read中所以可以避免加鎖,以此來(lái)提高并發(fā)讀的性能。

sync.Map的寫操作方法為Store方法

func (m *Map) Store(key, value any) {
   // 1.從read中查詢數(shù)據(jù)是否已經(jīng)存在,如果存在則嘗試修改
   read, _ := m.read.Load().(readOnly)
   if e, ok := read.m[key]; ok && e.tryStore(&value) {
      // 1.1 read中存在數(shù)據(jù),且更新完成,直接返回
      return
   }
   //2.read中沒(méi)有,準(zhǔn)備操作dirty,為保證線程安全加鎖
   m.mu.Lock()
   read, _ = m.read.Load().(readOnly)
   if e, ok := read.m[key]; ok {
   // 3.如果在read中查詢到數(shù)據(jù),則檢查是否已經(jīng)被標(biāo)記為刪除,
      if e.unexpungeLocked() {
      // 3.1 如果被標(biāo)記為刪除需要清空標(biāo)記并加入到dirty中
         m.dirty[key] = e
      }
      // 3.2 更新entry的值
      e.storeLocked(&value)
   } else if e, ok := m.dirty[key]; ok {
      // 4 如果存在于dirty中,則直接更新entry的值
      e.storeLocked(&value)
   } else {
   // 5 如果之前這個(gè)key不存在,則將新的key-value加入到dirty中
      if !read.amended {
      // 5.1 如果read.amended標(biāo)記顯示之前dirty中不存在read中沒(méi)有的key,則重置dirty,并標(biāo)amended為true
         // 5.1.1 將read中所有未被標(biāo)記為刪除的entry重新加入到dirty中
         m.dirtyLocked()
         // 5.1.2 更新read.amended標(biāo)記
         m.read.Store(readOnly{m: read.m, amended: true})
      }
      // 5.2 將新的key-value加入到dirty中
      m.dirty[key] = newEntry(value)
   }
   m.mu.Unlock()
}
// 判斷entry是否被標(biāo)記為刪除,如果是將其修改為nil
func (e *entry) unexpungeLocked() (wasExpunged bool) {
   return atomic.CompareAndSwapPointer(&e.p, expunged, nil)
}

寫操作分為以下幾種情況:

  • 數(shù)據(jù)在read中
  • 數(shù)據(jù)在read中但已經(jīng)被標(biāo)記為刪除
  • 數(shù)據(jù)在dirty中
  • 一個(gè)全新的數(shù)據(jù)

當(dāng)數(shù)據(jù)在read中時(shí),Store會(huì)嘗試通過(guò)原子操作修改數(shù)據(jù),如果原子操作成功,則相當(dāng)于數(shù)據(jù)更新完成;

具體的代碼如下:

func (e *entry) tryStore(i *any) bool {
   for {
       // 1.獲取entry具體的值
      p := atomic.LoadPointer(&e.p)
      // 2.如果數(shù)據(jù)已經(jīng)被標(biāo)記刪除,則返回false
      if p == expunged {
         return false
      }
      // 3.更新當(dāng)前值,并返回true
      if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) {
         return true
      }
   }
}

當(dāng)數(shù)據(jù)在read中已經(jīng)被標(biāo)記為刪除,此時(shí)需要重新將entry加入到dirty中,并更新值(這里本質(zhì)上增加了一個(gè)新的entry只是服用了之前entry的地址空間)

當(dāng)數(shù)據(jù)在dirty中時(shí),則直接通過(guò)原子操作更新entry的指針;

當(dāng)數(shù)據(jù)是一個(gè)新數(shù)據(jù)時(shí),會(huì)創(chuàng)建一個(gè)新的entry加入到dirty中,并且如果是dirty中的第一個(gè)數(shù)據(jù)則會(huì)執(zhí)行dirtyLocked方法,將read中當(dāng)前的數(shù)據(jù)(未標(biāo)記刪除的)加入到dirty中。

dirtyLocked的具體實(shí)現(xiàn)如下:

func (m *Map) dirtyLocked() {
    //1 dirty之前是nil的情況才可以進(jìn)行重置操作
   if m.dirty != nil {
      return
   }
   // 2 獲取read中的數(shù)據(jù)
   read, _ := m.read.Load().(readOnly)
   // 3 初始化dirty
   m.dirty = make(map[any]*entry, len(read.m))
   // 4 遍歷read
   for k, e := range read.m {
      // 4.1 將非nil且未被標(biāo)記為刪除的對(duì)象加入到dirty中
      if !e.tryExpungeLocked() {
         m.dirty[k] = e
      }
   }
}
// 判斷entry是否被標(biāo)記為刪除,如果entry的值為nil,則將其標(biāo)記為刪除
func (e *entry) tryExpungeLocked() (isExpunged bool) {
    // 1 獲取entry的值
   p := atomic.LoadPointer(&e.p)
   for p == nil {
       // 2 如果當(dāng)前entry的值為空,則嘗試將此key標(biāo)記為刪除
      if atomic.CompareAndSwapPointer(&e.p, nil, expunged) {
         return true
      }
      p = atomic.LoadPointer(&e.p)
   }
   // 3 判斷p是否為被標(biāo)記為刪除
   return p == expunged
}

通過(guò)上面的分析我們可以發(fā)現(xiàn)當(dāng)寫操作頻繁時(shí)存在以下幾個(gè)問(wèn)題

  • dirty中存在大量數(shù)據(jù),而read的查詢會(huì)大概率無(wú)法命中,從而導(dǎo)致查詢需要查詢r(jià)ead和dirty兩個(gè)map且有額外的冗余操作,所以讀性能被大大降低
  • 頻繁的無(wú)法命中導(dǎo)致dirty數(shù)據(jù)的晉升,雖然晉升時(shí)只是進(jìn)行指針切換及dirty的清空,但每次晉升后的第一次寫入都會(huì)導(dǎo)致dirty對(duì)read進(jìn)行拷貝,大大降低性能。
  • 每次寫操作為了因?yàn)椴淮_定數(shù)據(jù)是在read還是dirty或者新數(shù)據(jù)需要進(jìn)行額外的檢查和操作
  • dirty中和read中在某些情況下存在數(shù)據(jù)重復(fù),內(nèi)存占用會(huì)高一些

綜上,在寫操作比較頻繁的時(shí)候,sync.Map的各方面性能都大大降低;而對(duì)于一些只有極少寫操作的數(shù)據(jù)(比如:只在服務(wù)器啟動(dòng)時(shí)加載一次的表格數(shù)據(jù)),sync.Map可以提高并發(fā)操作的性能。

如何刪除數(shù)據(jù)

在上面的dirtyLoacked方法中我們看到當(dāng)初始化dirty后,會(huì)遍歷read中的數(shù)據(jù),將非nil且未被標(biāo)記為刪除的對(duì)象加入到dirty中。由此可以看出read中的數(shù)據(jù)在刪除時(shí)并不會(huì)立刻刪除只是將對(duì)象標(biāo)記為nil或者expunged。

具體代碼如下:(Delete方法本質(zhì)上就是執(zhí)行的LoadAndDelete)

func (m *Map) LoadAndDelete(key any) (value any, loaded bool) {
   read, _ := m.read.Load().(readOnly)
   // 1 從read中查詢數(shù)據(jù)
   e, ok := read.m[key]
   // 2 如果read中不存在,且amended標(biāo)記顯示dirty中存在read中沒(méi)有的key,則去dirty中查詢
   if !ok && read.amended {
      // 3.1開始操作dirty,需要加鎖保證線程安全
      m.mu.Lock()
      read, _ = m.read.Load().(readOnly)
      // 3.2 重新從read中檢查一次,避免在Lock執(zhí)行前dirty中的數(shù)據(jù)觸發(fā)了晉升到read的操作
      e, ok = read.m[key]
      if !ok && read.amended {
         // 3.3 從dirty中查詢
         e, ok = m.dirty[key]
         // 3.4 從dirty中刪除
         delete(m.dirty, key)
         // 3.5 無(wú)論是否從dirty中查詢到數(shù)據(jù),都相當(dāng)于從read中miss了,需要更新miss計(jì)數(shù)(滿足條件后會(huì)觸發(fā)dirty晉升)
         m.missLocked()
      }
      m.mu.Unlock()
   }
   // 4 和load一樣,這里ok為true可能是在read中讀取到數(shù)據(jù)或者dirty中讀取到數(shù)據(jù),
   // dirty中的話雖然已經(jīng)刪除但需要清空entry中的指針p
   if ok {
      // 5 標(biāo)記刪除
      return e.delete()
   }
   return nil, false
}

如上述代碼第5部分所示,無(wú)論entry存在哪里,最終都需要將entry標(biāo)記為刪除。如果存在read中會(huì)在dirty初始化時(shí)不被加入到dirty中,當(dāng)dirty再次晉升時(shí)read中的數(shù)據(jù)也就被拋棄了。如果存在dirty中則直接清空了數(shù)據(jù)并標(biāo)記entry被刪除。

sync.Map的Range方法

sync.Map并不支持遍歷,但卻提供了一個(gè)Range方法,此方法并不是和range關(guān)鍵字一樣對(duì)map的遍歷。

Range方法的具體作用:

  • 遍歷所有read中的元素,對(duì)其中的每個(gè)元素執(zhí)行函數(shù)f
  • 如果當(dāng)任何一個(gè)元素作為參數(shù)執(zhí)行函數(shù)f返回false,則立刻中斷遍歷

雖然在執(zhí)行初始階段Range會(huì)將dirty的數(shù)據(jù)晉升一次,但仍然不能保證在執(zhí)行過(guò)程中沒(méi)有新的數(shù)據(jù),所以Range只是遍歷了最新的read中的數(shù)據(jù),而非全部數(shù)據(jù)。

// 遍歷sync.Map
func (m *Map) Range(f func(key, value any) bool) {
   read, _ := m.read.Load().(readOnly)
   // 1 如果存在未晉升的數(shù)據(jù),則先進(jìn)行一次dirty數(shù)據(jù)晉升
   if read.amended {
      m.mu.Lock()
      read, _ = m.read.Load().(readOnly)
      if read.amended {
         read = readOnly{m: m.dirty}
         m.read.Store(read)
         m.dirty = nil
         m.misses = 0
      }
      m.mu.Unlock()
   }
   // 2 遍歷read中的所有entry,分別執(zhí)行f函數(shù)
   for k, e := range read.m {
      v, ok := e.load()
      if !ok {
         continue
      }
      // 3 當(dāng)某個(gè)entery執(zhí)行f函數(shù)返回false,則中斷遍歷
      if !f(k, v) {
         break
      }
   }
}

其他

問(wèn):什么時(shí)候清除被標(biāo)記刪除的value

答:當(dāng)首次向dirty中存入數(shù)據(jù)時(shí),會(huì)觸發(fā)dirty復(fù)制read中的內(nèi)容,此時(shí)再?gòu)?fù)制時(shí)只復(fù)制了非nil且未被標(biāo)記刪除的entry,當(dāng)dirty再次晉升時(shí)就覆蓋掉了read中的數(shù)據(jù),實(shí)現(xiàn)被標(biāo)記刪除的entry的刪除。

以上就是Golang sync.Map底層實(shí)現(xiàn)場(chǎng)景示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Golang sync.Map底層實(shí)現(xiàn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Golang 空結(jié)構(gòu)體特性與用法詳解

    Golang 空結(jié)構(gòu)體特性與用法詳解

    本文主要介紹了Golang 空結(jié)構(gòu)體特性與用法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2026-03-03
  • Golang實(shí)現(xiàn)按行讀取文件的方法小結(jié)

    Golang實(shí)現(xiàn)按行讀取文件的方法小結(jié)

    按行讀取文件相較于一次性載入,有著很多優(yōu)勢(shì),如內(nèi)存效率高、處理速度快、實(shí)時(shí)性高等,本文主要介紹了Golang按行讀取文件的相關(guān)方法,希望對(duì)大家有所幫助
    2024-02-02
  • 一文帶你了解Go中的內(nèi)存對(duì)齊

    一文帶你了解Go中的內(nèi)存對(duì)齊

    一旦涉及到較為底層的編程,特別是與硬件交互,內(nèi)存對(duì)齊是一個(gè)必修的課題,所以這篇文章小編就想來(lái)和大家聊一聊Go語(yǔ)言中的內(nèi)存對(duì)齊,希望對(duì)大家有所幫助
    2023-10-10
  • Go使用github.com/google/uuid生成唯一ID的操作方案

    Go使用github.com/google/uuid生成唯一ID的操作方案

    這篇文章介紹了Go語(yǔ)言中常用的UUID庫(kù)github.com/google/uuid,詳細(xì)講解了UUID的定義、特點(diǎn)、生成方法、轉(zhuǎn)換、解析、應(yīng)用場(chǎng)景以及性能情況,需要的朋友可以參考下
    2026-03-03
  • 使用golang引入外部包的三種方式:go get, go module, vendor目錄

    使用golang引入外部包的三種方式:go get, go module, ve

    這篇文章主要介紹了使用golang引入外部包的三種方式:go get, go module, vendor目錄,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • Golang使用Gin框架實(shí)現(xiàn)http分塊傳輸

    Golang使用Gin框架實(shí)現(xiàn)http分塊傳輸

    這篇文章主要為大家詳細(xì)介紹了Golang中如何使用Gin框架實(shí)現(xiàn)http分塊傳輸功能,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,需要的可以參考一下
    2023-05-05
  • Golang中channel使用的一些小技巧

    Golang中channel使用的一些小技巧

    這篇文章主要介紹了Golang中channel使用的一些小技巧,本文講解了關(guān)閉2次、讀取的時(shí)候channel提前關(guān)閉了、向已經(jīng)關(guān)閉的channel寫數(shù)據(jù)等技巧及這實(shí)例代碼,需要的朋友可以參考下
    2015-07-07
  • golang遠(yuǎn)程操作docker api方式

    golang遠(yuǎn)程操作docker api方式

    文章介紹了Docker監(jiān)聽(tīng)并處理三種socket形式的API請(qǐng)求:unix、tcp和fd,默認(rèn)情況下,Docker已經(jīng)開啟了unix socket,并且只有root用戶或docker用戶組成員才有權(quán)限訪問(wèn),用戶可以通過(guò)編輯docker守護(hù)進(jìn)程的配置文件,添加tcp參數(shù)來(lái)開放遠(yuǎn)程API訪問(wèn)
    2025-12-12
  • Windows上安裝Go并配置環(huán)境變量(圖文步驟)

    Windows上安裝Go并配置環(huán)境變量(圖文步驟)

    開始使用Go創(chuàng)建應(yīng)用程序之前,需要設(shè)置開發(fā)環(huán)境,本文主要介紹了Windows上安裝Go并配置環(huán)境變量,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-08-08
  • Go語(yǔ)言sync.Pool對(duì)象池使用場(chǎng)景基本示例

    Go語(yǔ)言sync.Pool對(duì)象池使用場(chǎng)景基本示例

    這篇文章主要為大家介紹了Go語(yǔ)言sync.Pool對(duì)象池使用場(chǎng)景的基本示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12

最新評(píng)論

菏泽市| 临沧市| 延吉市| 武乡县| 嘉义市| 宜宾县| 色达县| 大余县| 德清县| 丰县| 迁安市| 射洪县| 扎赉特旗| 嵊州市| 阿鲁科尔沁旗| 惠来县| 青铜峡市| 日照市| 霍城县| 东至县| 武功县| 临安市| 宜昌市| 洛扎县| 乌审旗| 偃师市| 建始县| 华宁县| 滦南县| 汽车| 香河县| 鱼台县| 长宁县| 当阳市| 乐平市| 永嘉县| 台安县| 鄂托克旗| 武义县| 维西| 子长县|