当前位置:网站首页>[golang] quick review guide quickreview (VIII) -- goroutine

[golang] quick review guide quickreview (VIII) -- goroutine

2022-06-23 20:04:00 DDGarfield

goroutine yes Golang specific , Similar to threads , But threads are scheduled and managed by the operating system , and goroutine By Golang User state threads that perform scheduling management at runtime .

1.C# Thread operation of

1.1 Create thread

 static void Main(string[] args)
 {
     Thread thread = new Thread(Count);
     thread.IsBackground = true;
     thread.Start();
     for (int i = 0; i < 10; i++)
         Console.Write("x\n");
 }
 static void Count()
 {
     for (int i = 0; i < 100; i++)
     {
         Console.WriteLine(i); ;
     }
 }

1.2 Pass parameters to thread

Thread The constructor has two parameters ParameterizedThreadStart And ThreadStart

public delegate void ParameterizedThreadStart(object? obj);
public delegate void ThreadStart();

you 're right , One is nonparametric delegation ,, One is a parameterized delegate and the parameter type is object, Therefore, the method parameter we use to create the thread needs to be object type , And then inside the object Type parameters are converted :

static void Main(string[] args)
{
    Thread thread = new Thread(Count);
    thread.IsBackground = true;
    thread.Start(100);
    for (int i = 0; i < 10; i++)
        Console.Write("x\n");
}


static void Count(object times)
{
    int count = (int)times;
    for (int i = 0; i < 100; i++)
    {
        Console.WriteLine(i); ;
    }
}

Of course The most simple Or use it directly lambda expression

static void Main(string[] args)
{
    Thread thread = new Thread(()=>{
        Count(100);
    });
    thread.IsBackground = true;
    thread.Start();
    for (int i = 0; i < 10; i++)
        Console.Write("x\n");
}

static void Count(object times)
{
    int count = (int)times;
    for (int i = 0; i < 100; i++)
    {
        Console.WriteLine(i); ;
    }
}

Be careful : Use Lambda The expression can be simply given to Thread Pass parameters , But after the thread starts , You may accidentally modify the captured variables , This needs more attention . For example, in the loop body , It is best to create a temporary variable .

1.3 Thread safety and locking

Look at thread safety from the singleton mode :

class Student
{
    private static Student _instance =new Student();
    private Student()
    {
    }
    static Student GetInstance()
    {
        return _instance;
    }
}

** The singleton pattern , Our intention is to always keep the class instantiated once and then ensure that there is only one instance in memory .** In the above code, when the class is loaded , Complete the initialization of static private variables , Whether it's needed or not , Will be instantiated , This is called Single case mode of hungry man mode . Although there is no thread safety problem , But if this class does not use , There is no need to instantiate . Then we have the following expression : When needed , Just instantiate

class Student
{
    private static Student _instance;
    private Student()
    {
    }
    static Student GetInstance()
    {
        if (_instance == null) 
                        _instance = new Student();
        return _instance;
    }
}

The above code , Judge whether the static private variable is empty when calling , And then assign a value . In fact, there is a thread safety problem : Multithreaded call GetInstance(), When multiple threads execute at the same time , Conditions _instance == null May be satisfied at the same time . such _instance This completes multiple instantiation assignment operations , It leads to our lock Lock

private static Student _instance;
private static readonly object locker = new object();
private Student()
{

}
static Student GetInstance()
{
    lock (locker)
    {
        if (_instance == null)
            _instance = new Student();
    }

    return _instance;
}
  • The first thread runs , It will be locked Lock
  • The second thread runs , First detected locker The object is " Lock " state ( Whether there are threads in lock Inside , Not done ), The thread will block waiting for the first thread to unlock
  • The first thread is finished lock Body code , Unlock , The second thread will continue to execute

It seems to be perfect , But in the case of multithreading , Every time I have to go through , testing ( Is it blocked ), locked , Unlock , In fact, it affects the performance , Our original goal was to return a single instance .** We are testing locker Before the object is locked , If the instance already exists , Then the follow-up work is unnecessary .** So there's the following Double check

private static Student _instance;
private static readonly object locker = new object();
private Student()
{

}
static Student GetInstance()
{
    if (_instance == null)
    {
        lock (locker)
        {
            if (_instance == null)
                _instance = new Student();
        }
    }

    return _instance;
}

1.4 Task

Threads have two types of work :

  • CPU-Bound Computationally intensive , Spend most of your time executing CPU Operation of intensive work , This type of work never leaves a thread waiting .
  • IO-BoundI/O intensive , The act of spending most of your time waiting for something to happen , I've been waiting , The type of work that causes the thread to enter the waiting state . Such as through http Request access to resources .

about IO-Bound The operation of , Time is spent mainly on I/O On , And in the CPU It takes almost no time . For this , More recommended Task Write asynchronous code , And for CPU-Bound and IO-Bound Asynchronous code is not the focus of this article , The blogger will give an overview of Task The advantages of :

  • Stop waiting :: With HttpClient Use asynchronous code to request GetStringAsync For example , This is obviously a I/O-Bound, Finally, the local network library of the operating system will be called , System API call , For example, the person who initiated the request socket, But the length of time is not determined by the code , It depends on the hardware , operating system , Network situation . Control is returned to the caller , We can do other things , This allows the system to handle more work instead of waiting I/O End of call . until await To get the result of the request .
  • Let the caller stop waiting : about CPU-Bound, There is no way to avoid using a thread for computation , Because this is a computing intensive job after all . But use Task The asynchronous code of (async And await) It can not only interact with background threads , You can also let the caller continue to respond ( Other operations can be performed concurrently ). ditto , Until I met await when , Asynchronous methods give way to callers .

2.Golang Of goroutine

2.1 start-up goroutine

Golang Start one in goroutine No, C# The thread of is so troublesome , Just add the keyword before the calling method go.

func main(){
    go Count(100)
}
func Count(times int) {
 for i := 0; i < times; i++ {
  fmt.Printf("%v\n", i)
 }
}

2.2 goroutine Synchronization of

stay C# The task (Task) have access to Task.WhenAll To wait for Task Object complete . And in the Golang It looks simple and rough , It will use sync Bag WaitGroup, Then make a register like a roster :

  • start-up - Count +1
  • completion of enforcement - Count -1
  • complete
package main

import (
 "fmt"
 "sync"
)

var wg sync.WaitGroup

func main() {
    
    // Register 
 wg.Add(2)
 go Count(100)
 go Count(100)
    
    // Waiting for all registered goroutine It's all over 
 wg.Wait()
 fmt.Println(" Execution completed ")
}

func Count(times int) {
    
    // Execution completed 
 defer wg.Done()
 for i := 0; i < times; i++ {
  fmt.Printf("%v\n", i)
 }
}

2.3 channel passageway

One goroutine Send a specific value to another goroutine Our communication mechanism is channel

2.3.1 channel Statement

channel Is a reference type

var  Variable  chan  Element type 

chan Element type Follow struct,interface equally , Is a type . The following element types define the channel specific storage types .

2.3.2 channel initialization

After the statement channel It's a null value nil, Initialization is required to use .

var ch chan int
ch=make(chan int,5) //5 Buffer size set for , Optional parameters 

2.3.3 channel operation

Operate the three board axe :

  • send - send out ch<-100

The arrow points from the value to the channel

  • receive - receive value:=<-ch
i, ok := <-ch1 //  After the channel is closed, the value can be taken ok=false

// perhaps 
for i := range ch1 { //  When the channel is closed, it exits for range loop 
    fmt.Println(i)
}

The arrow points from the channel to the variable

  • close - close close(ch)

2.3.3 Buffered and unbuffered

ch1:=make(chan int,5) //5 Buffer size set for , Optional parameters 
ch2:=make(chan int)

Unbuffered channels , Unbuffered channels can only send values when they receive them .

  • Only pass values to the channel , Do not receive from channel , Will appear deadlock
  • Receive only from the channel , Do not send to the channel , Blocking can also occur

The use of unbuffered channels for communication will result in the transmission and reception of goroutine Synchronization . therefore , Unbuffered channels are also called Synchronous channel .

There are buffered channels , It can effectively alleviate the embarrassment of unbuffered channels , But the passage is full , The above embarrassment still exists .

2.3.4 One way passage

Restrict channels to send or receive only in functions , One way passage make oneself up and go on the stage , The one-way channel is used in the parameters of the function , No new keywords have been introduced , Simply change the position of the arrow :

chan<- int  Write but not read 
<-chan int  Read only don't write 

Function transfer parameter and any assignment operation can A two-way channel is converted to a one-way channel , conversely , no way .

2.3.5 Multiplexing

package main

import "fmt"

func main() {
 ch := make(chan int, 1)
 for i := 0; i < 10; i++ {
  select {
  case x := <-ch:
   fmt.Printf(" The first %v Time ,x := <-ch, Read the value from the channel %v", i+1, x)
   fmt.Println()
  case ch <- i:
   fmt.Printf(" The first %v Time , perform ch<-i", i+1)
   fmt.Println()
  }
 }
}

 The first 1 Time , perform ch<-i
 The first 2 Time ,x := <-ch, Read the value from the channel 0
 The first 3 Time , perform ch<-i
 The first 4 Time ,x := <-ch, Read the value from the channel 2
 The first 5 Time , perform ch<-i
 The first 6 Time ,x := <-ch, Read the value from the channel 4
 The first 7 Time , perform ch<-i
 The first 8 Time ,x := <-ch, Read the value from the channel 6
 The first 9 Time , perform ch<-i
 The first 10 Time ,x := <-ch, Read the value from the channel 8

Select The rules of multiplexing :

  • Can handle one or more channel Sending of / Receive operation .
  • If more than one case At the same time satisfy ,select Will randomly choose one .
  • For no case Of select{} Will be waiting , It can be used to block main function .

2.5 Concurrent security and locking

goroutine It's through channel Channel for communication . No concurrency security issues . however , In fact, the operation of public resources cannot be completely avoided , If more than one goroutine Operate these public resources at the same time , Concurrency security issues may occur , Follow C# Same thread as , The emergence of locks is to solve this problem :

2.5.1 The mutex

The mutex , This is just like C# The mechanism of the lock is the same , One goroutine visit , The other can only wait for the release of the mutex . Also need to sync package :

sync.Mutex

var lock sync.Mutex

lock.Lock()// Lock 

// Operate common resources 

lock.Unlock()// Unlock 

2.5.2 Read write mutex

A mutex is completely mutex , If you read more and write less , Most of the goroutine All reading , A small amount of goroutine Writing , There is no need to lock concurrent reads . When using , Still need sync package :

sync.RWMutex

There are two types of read-write locks :

  • Read the lock
  • Write lock
import (
 "fmt"
 "sync"
)

var (
 lock   sync.Mutex
 rwlock sync.RWMutex
)

rwlock.Lock() //  Add write lock 

// The effect is equivalent to mutex 
rwlock.Unlock() //  Unlock the lock 

rwlock.RLock()  // Add read lock 

// Can't read or write 
rwlock.RUnlock() // Interpreting locks 

2.6* sync.Once

goroutine We have used the synchronization of sync.WaitGroup

  • Add(count int) Counter accumulation , Calling goroutine External execution , Designated by the developer
  • Done() Counter -1, stay goroutine Internal implementation
  • Wait() Blocking Until the counter is 0

And then there's another one sync.Once, seeing the name of a thing one thinks of its function , once , Only once .

func (o *Once) Do(f func()) {}

var handleOnce sync.Once
handleOnce.Do( function )

sync.Once In fact, it contains a mutex and a Boolean value , Mutexes guarantee the security of Boolean values and data , and Boolean value Used to record whether initialization is completed . In this way, the design can ensure that the initialization operation is concurrent and safe , And initialization will not be performed more than once .

type Once struct {
 // done indicates whether the action has been performed.
 // It is first in the struct because it is used in the hot path.
 // The hot path is inlined at every call site.
 // Placing done first allows more compact instructions on some architectures (amd64/x86),
 // and fewer instructions (to calculate offset) on other architectures.
 done uint32
 m    Mutex
}

2.7* sync.Map

Golang Of map It's not concurrent security .sync The package provides an out of the box version of concurrent security map–sync.Map.

Out of the box no need Like built-in map The use of make function initialization It can be used directly . meanwhile sync.Map Built in things like StoreLoadLoadOrStoreDeleteRange And so on .

var m = sync.Map{}
m.Store(" sichuan ", " Chengdu ")
m.Store(" Chengdu ", " High-tech zone ")
m.Store(" High-tech zone ", " Yinglong South 1st Road ")
m.Range(func(key interface{}, value interface{}) bool {
    fmt.Printf("k=:%v,v:=%v\n", key, value)
    return true
})
fmt.Println()
v, ok := m.Load(" Chengdu ")
if ok {
    fmt.Println(v)
}
fmt.Println()
value, loaded := m.LoadOrStore(" shaanxi ", " Xi'an ")
fmt.Println(value)
fmt.Println(loaded)
m.Range(func(key interface{}, value interface{}) bool {
    fmt.Printf("k=:%v,v:=%v\n", key, value)
    return true
})
fmt.Println()

//【 Value taking and creation 】
// Load when it exists , Add... If it doesn't exist 
value1, loaded1 := m.LoadOrStore(" sichuan ", " Chengdu ")
fmt.Println(value1)
fmt.Println(loaded1)
m.Range(func(key interface{}, value interface{}) bool {
    fmt.Printf("k=:%v,v:=%v\n", key, value)
    return true
})
fmt.Println()

//【 Delete 】
// Load and delete  key There is 
value2, loaded2 := m.LoadAndDelete(" sichuan ")
fmt.Println(value2)
fmt.Println(loaded2)
m.Range(func(key interface{}, value interface{}) bool {
    fmt.Printf("k=:%v,v:=%v\n", key, value)
    return true
})
fmt.Println()

// Load and delete  key  non-existent 
value3, loaded3 := m.LoadAndDelete(" Beijing ")
fmt.Println(value3)
fmt.Println(loaded3)
m.Range(func(key interface{}, value interface{}) bool {
    fmt.Printf("k=:%v,v:=%v\n", key, value)
    return true
})
fmt.Println()

m.Delete(" Chengdu ")  // The interior is called LoadAndDelete

//【 Traverse 】
m.Range(func(key interface{}, value interface{}) bool {
    fmt.Printf("k=:%v,v:=%v\n", key, value)
    return true
})
fmt.Println()
k=: sichuan ,v:= Chengdu 
k=: Chengdu ,v:= High-tech zone 
k=: High-tech zone ,v:= Yinglong South 1st Road 

 High-tech zone 

 Xi'an 
false
k=: sichuan ,v:= Chengdu 
k=: Chengdu ,v:= High-tech zone 
k=: High-tech zone ,v:= Yinglong South 1st Road 
k=: shaanxi ,v:= Xi'an 

 Chengdu 
true
k=: sichuan ,v:= Chengdu 
k=: Chengdu ,v:= High-tech zone 
k=: High-tech zone ,v:= Yinglong South 1st Road 
k=: shaanxi ,v:= Xi'an 

 Chengdu 
true
k=: shaanxi ,v:= Xi'an 
k=: Chengdu ,v:= High-tech zone 
k=: High-tech zone ,v:= Yinglong South 1st Road 

<nil>
false
k=: Chengdu ,v:= High-tech zone 
k=: High-tech zone ,v:= Yinglong South 1st Road 
k=: shaanxi ,v:= Xi'an 

k=: High-tech zone ,v:= Yinglong South 1st Road 
k=: shaanxi ,v:= Xi'an 

2.8 The singleton pattern

Sum up ,sync.Once In fact, it contains a mutex and a Boolean value , This Boolean value is equivalent to C# The first judgment of double test in singleton mode . So in golang Available in sync.Once Implement singleton mode :

package singleton

import (
    "sync"
)

type singleton struct {}

var instance *singleton
var once sync.Once

func GetInstance() *singleton {
    once.Do(func() {
        instance = &singleton{}
    })
    return instance
}

2.9* Atomic manipulation

The lock operation in the code is time-consuming because it involves context switching in kernel state 、 The cost is relatively high . For basic data types, we can also use atomic operations to ensure concurrency security .Golang Atomic operations in are performed by the built-in standard library sync/atomic Provide . Because there are few scenes , No introduction , For detailed operation, please consult and learn by yourself .

原网站

版权声明
本文为[DDGarfield]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/174/202206231941491570.html

随机推荐