go語言-空結構體/ chan struct{}

空結構體

空結構體的寬度是0,佔用了0字節的內存空間。

var s struct{}
fmt.Println(unsafe.Sizeof(s)) // prints 0

由於空結構體佔用0字節,那麼空結構體也不需要填充字節。所以空結構體組成的組合數據類型也不會佔用內存空間。

 type S struct {

        A struct{}

        B struct{}

    }

    var s S

    fmt.Println(unsafe.Sizeof(s)) // prints 0

0人點贊
Go

chan struct{}

通過消息來共享數據是golang的一種設計哲學,channel則是這種哲理的體現。

golang中的空結構體 channel := make(chan struct{})
特點

  • 省內存,尤其在事件通信的時候。
  • struct零值就是本身,讀取close的channel返回零值

常用用法
通常struct{}類型channel的用法是使用同步,一般不需要往channel裏面寫數據,只有讀等待,而讀等待會在channel被關閉的時候返回。

type Miner struct {
	api api.FullNode

	epp gen.WinningPoStProver

	lk       sync.Mutex
	address  address.Address
	stop     chan struct{}
	stopping chan struct{}

	waitFunc waitFunc

	lastWork *MiningBase

	minedBlockHeights *lru.ARCCache
}

stop 它是一個管道chan,內部的數據類型是struct{}。

單獨拿struct{}來說,我們熟悉type Name struct{a int, b bool}這樣去定義一個結構體的類型,其實struct{…}就是定義結構體,和map[string]int這種定義是一樣的,type只是給它取了一個別名。 總結: 實際上struct{}就是一種普通數據類型,只是沒有具體的值而已。

注意,channel對象一定要make出來才能使用。, 如下,make後賦值給m

func (m *Miner) Start(ctx context.Context) error {
	m.lk.Lock()
	defer m.lk.Unlock()
	if m.stop != nil {
		return fmt.Errorf("miner already started")
	}
	m.stop = make(chan struct{})
	go m.mine(context.TODO())
	return nil
}

func (m *Miner) Stop(ctx context.Context) error {
	m.lk.Lock()
	defer m.lk.Unlock()

	m.stopping = make(chan struct{})
	stopping := m.stopping
	close(m.stop)

	select {
	case <-stopping:
		return nil
	case <-ctx.Done():
		return ctx.Err()
	}
}

使用場景
首先事件通知,可以通過寫入 通知其他協程,但是隻能通知一個。

channel := make(chan struct{})
go func() {
    // ... do something
    channel <- struct{}{}
}()
fmt.Println(<-channel)

和close進行配合,通知所有相關協程。

在讀入被close的channel返回零值,正常的協程是讀取不到這個close的。
close之後,所有協程都可以讀到。

比較經典的例子就是用於stopChan作爲停止channel通知所有協程。

在下面的例子中 我們可以通過s.Stop()通知所有的serverHandler協程停止工作,並且等待他們正常退出。

type Server struct {
    serverStopChan chan struct{}
    stopWg         sync.WaitGroup
}
func (s *Server) Stop() {
    if s.serverStopChan == nil {
        panic("gorpc.Server: server must be started before stopping it")
    }
    close(s.serverStopChan)
    s.stopWg.Wait()
    s.serverStopChan = nil
}
func serverHandler(s *Server){
    for {
        select {
        case <-s.serverStopChan:
            return
        default:
            // .. do something
        }
    }
}

帶緩衝的chan struct{}數據讀寫

另外也可以定義帶緩衝的channel

package main

import (
    "time"
    "log"
)

var ch chan struct{} = make(chan struct{}, 2)

func foo() {
    ch <- struct{}{}
    log.Println("foo() 000");
    ch <- struct{}{}
    log.Println("foo() 111");
    time.Sleep(5 * time.Second)
    log.Println("foo() 222");
    close(ch)
    log.Println("foo() 333");
}

func main() {
    var b struct{}
 
    log.Println("main() 111");
    go foo()
    log.Println("main() 222");
    a := <-ch
    log.Println("main() 333", a);
    b  = <-ch
    log.Println("main() 444", b);
    c := <-ch
    log.Println("main() 555", c);
}

<-ch用來從channel ch中接收數據,這個表達式會一直被block,直到有數據可以接收。

從一個nil channel中接收數據會一直被block。(往nil channel中發送數據會一致被阻塞着。)

從一個被close的channel中接收數據不會被阻塞,而是立即返回,接收完已發送的數據後會返回元素類型的零值(zero value)。

如前所述,你可以使用一個額外的返回參數來檢查channel是否關閉。

x, ok := <-ch
x, ok = <-ch
var x, ok = <-ch

如果OK 是false,表明接收的x是產生的零值,這個channel被關閉了或者爲空。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章