SoFunction
Updated on 2025-03-05

Go gets an instance of a coroutine number

I won't say much nonsense, let's just read the code~

func GetGID() uint64 {
    b := make([]byte, 64)
    b = b[:(b, false)]
    b = (b, []byte("goroutine "))
    b = b[:(b, ' ')]
    n, _ := (string(b), 10, 64)
    return n
}

Supplement: Go concurrent coroutine and channel

Go concurrent coroutine

1.1 Go language competition status

If there is concurrency, there is resource competition. If two or more goroutines access a shared resource without synchronizing each other, for example, when reading and writing the resource at the same time, it will be in a competing state. This is resource competition in concurrency.

Concurrency itself is not complicated, but because of the problem of resource competition, it makes us more complicated to develop good concurrency programs because it will cause many inexplicable problems.

The following code will show a competition state:

import (
  "fmt"
  "runtime"
  "sync"
)
var (
  count int32
  wg  
)
func main() {
  (2)
  go incCount()
  go incCount()
  ()
  (count)
}
func incCount() {
  defer ()
  for i := 0; i < 2; i++ {
    value := count
    ()
    value++
    count = value
  }
}

The count variable does not have any synchronization protection, so both goroutines will read and write, which will cause the calculated results to be overwritten, resulting in the error result.

The () in the code means to pause the current goroutine, return to the execution queue runq, and let other waiting goroutines run. The purpose is to make the result of resource competition more obvious. The next time you run the paused goroutine, start at the breakpoint.

Analyze the program running process:

g1 reads the value of count to 0;

Then g1 paused, switched to g2 to run, and g2 reads the value of count as well;

g2 pauses, switch to the position where g1 pauses and continues to run. When g1 is count+1, the value of count becomes 1;

g1 pauses, switches to g2. g2 has just obtained the value 0, +1 to it, and finally assigned the value to count, and the result is still 1;

It can be seen that the result of g1 on count+1 is covered by g2, and both goroutines are +1 and the result is still 1.

From the above analysis, we can see that the above problem occurs because the two goroutines cover each other's results.

Therefore, our reading and writing of the same resource must be atomized, that is, we can only allow one goroutine to read and write operations on shared resources at the same time. The shared resource in this example is count

By generating an executable file through go build -race, and then running this executable file, you can detect resource competition information and see the printed detection information. as follows

==================
WARNING: DATA RACE
Read at 0x000000619cbc by goroutine 8:
 ()
   D:/code/src/:25 +0x80// goroutine 8 reads shared resource value in line 25 of code := countPrevious write at 0x000000619cbc by goroutine 7:
 ()
   D:/code/src/:28 +0x9f// goroutine 7 Modify shared resource count=value in line 28 of the codeGoroutine 8 (running) created at:
 ()
   D:/code/src/:17 +0x7e
Goroutine 7 (finished) created at:
 ()
   D:/code/src/:16 +0x66//The two goroutines are started from lines 16 and 17 of the main function through the go keyword.==================
4
Found 1 data race(s)

1.2 Locking shared resources

Go provides a traditional mechanism for synchronizing goroutines, which is to lock shared resources. Some functions in the atomic and sync packages can lock shared resources.

1.2.1 Atomic Functions

Atomic functions can synchronously access integer variables and pointers with a very underlying locking mechanism.

import (
  "fmt"
  "runtime"
  "sync"
  "sync/atomic"
)
var (
  counter int64
  wg   
)
func main() {
  (2)
  go incCounter(1)
  go incCounter(2)
  () //Waiting for the end of goroutine  (counter)
}
func incCounter(id int) {
  defer ()
  for count := 0; count &lt; 2; count++ {
    atomic.AddInt64(&amp;counter, 1) //Safely add 1 to counter    ()
  }
}

The above code uses the AddInt64 function of the atmoic package, which synchronizes the addition of integer values ​​by forcing only one gorountie to run at the same time and completes this addition operation.

Two other useful atomic functions are LoadInt64 and StoreInt64. These two functions provide a safe way to read and write an integer value. The following code uses the LoadInt64 and StoreInt64 functions to create a synchronization flag, which can notify multiple goroutines in the program of a special status.

import (
  "fmt"
  "sync"
  "sync/atomic"
  "time"
)
var (
  shutdown int64
  wg    
)
func main() {
  (2)
  go doWork("A")
  go doWork("B")
  (1 * )
  ("Shutdown Now")
  atomic.StoreInt64(&amp;shutdown, 1)
  ()
}
func doWork(name string) {
  defer ()
  for {
    ("Doing %s Work\n", name)
    (250 * )
    if atomic.LoadInt64(&amp;shutdown) == 1 {
      ("Shutting %s Down\n", name)
      break
    }
  }
}
--output--
Doing A Work
Doing B Work
Doing B Work
Doing A Work
Doing A Work
Doing B Work
Doing B Work
Doing A Work//The first 8 lines are different every time they runShutdown Now
Shutting A Down
Shutting B Down//AandBAllshut downback,Depend on()Set the counter0

In the above code, the main function uses the StoreInt64 function to safely modify the value of the shutdown variable. If a doWork goroutine tries to call the LoadInt64 function while the main function calls StoreInt64, the atomic function will synchronize these calls to ensure that these operations are safe and will not enter a contender state.

1.2.2 Lock

See the previous article. The above examples can be kept synchronized and cancel competition. You can follow the following operations:

func incCounter(id int) {
  defer ()
  for count := 0; count &lt; 2; count++ {
    //Only one goroutine is allowed to enter this critical area at the same time    ()
    {
      value := counter
      ()//Exit the current goroutine, and the scheduler will assign this goroutine again to continue running.      value++
      counter = value
    }
    () //Release the lock to allow other waiting goroutines to enter the critical area  }
}

1.3 Channel Chan

All goroutines at both ends of the channel are understood as producer-consumer model.

There are 4 writing methods for data reception in the channel.

Blocking reception of data

When receiving data in blocking mode, the received variable is used as the lvalue of the <- operator, in the format as follows:

data := <-ch

This statement will be blocked until data is received and assigned to the data variable.

2) Non-blocking reception data

When receiving data from a channel using a non-blocking method, the statement will not block, and the format is as follows:

data, ok := <-ch

data: Indicates the received data. When no data is received, data is the zero value of the channel type.

ok: indicates whether data has been received.

Non-blocking channel reception methods can cause high CPU usage and are therefore very little used. If you need to implement reception timeout detection, you can cooperate with select and timer channel for

3) Receive data in a loop

import (
  "fmt"
  "time"
)
func main() {
  // Build a channel, and you can have buffers here, because it is sent when it is collected, without blocking and waiting  ch := make(chan int)
  // Enable a concurrent anonymous function  go func() {
    // Loop from 3 to 0    for i := 3; i &gt;= 0; i-- {
      // Send values ​​between 3 and 0      ch &lt;- i
      // Wait for each sending      ()
    }
  }()
  // traversal of the receiving channel data  for data := range ch {
    // Print channel data    (data)
    // When data 0 is encountered, exit the receiving loop    if data == 0 {
        break
    }
  }
}
--output--

1.3.1 One-way channel

ch := make(chan int)
// Declare a channel type that can only be written to data, and assign a value to chvar chSendOnly chan&lt;- int = ch
or
ch := make(chan&lt;- int)
//Declare a channel type that can only read data, and assign it to chvar chRecvOnly &lt;-chan int = ch
or
ch := make(&lt;-chan int)

1.3.2 Elegant closure of the channel

1.3.3 Unbuffered channel

If both goroutines are not prepared at the same time, the channel causes goroutine blocking waiting for first performing send or receive operations. (Blocking means that for some reason the data has not arrived, the current coroutine (thread) is constantly in a waiting state, and the blocking is not unblocked until the condition is met.) This interaction behavior of sending and receiving the channel is itself synchronized. None of the operations can exist alone without the other operations.

In tennis, the two players pass the ball back and forth between the two. Players are always in one of two states, either waiting for the ball to catch or hit the ball to the opponent. Two goroutines can be used to simulate tennis matches and unbuffered channels to simulate ball back and forth

// This sample program shows how to simulate using unbuffered channels// Tennis match between 2 goroutinespackage main
import (
  "fmt"
  "math/rand"
  "sync"
  "time"
)
// wg is used to wait for the program to endvar wg 
func init() {
  (().UnixNano())
}
// main is the entrance to all Go programsfunc main() {
  // Create an unbuffered channel  court := make(chan int)
  // Add 2 to the count, which means you want to wait for two goroutines  (2)
  // Start two players  go player("Nadal", court)
  go player("Djokovic", court)
  // serve  court &lt;- 1
  // Wait for the game to end  ()
}
// player simulates a player playing tennisfunc player(name string, court chan int) {
  // Call Done when the function exits to notify the main function that the work has been completed  defer ()
  for {
    // Wait for the ball to be hit    ball, ok := &lt;-court
    if !ok {
      // If the channel is closed, we win      ("Player %s Won\n", name)
      return
    }
    // Select a random number and use this number to determine whether we lose the ball    n := (100)
    if n%13 == 0 {
      ("Player %s Missed\n", name)
      // Close the channel, which means we lost      close(court)
      return
    }
    // Show the number of hits and add 1 to the number of hits    ("Player %s Hit %d\n", name, ball)
    ball++
    // Hit the ball to the opponent, why is this sending the ball to another go coroutine?    //Because the court has no buffering, another go coroutine is waiting to receive the value in the court, so it turns to another go coroutine code at this time    court &lt;- ball
  }
}

1.3.4 Buffered channel

A buffered channel is a channel that can store one or more values ​​before being received. This type of channel does not force the transmission and reception of goroutines to be completed simultaneously. The blocking condition of transmission and acceptance is that the reception action will block only if there is no value to be received in the channel. The sending action will only block if the channel has no available buffer to accommodate the sent value.

There is a big difference between a buffered channel and an unbuffered channel: an unbuffered channel ensures that the goroutines that are sent and received will exchange data at the same time; a buffered channel does not have this guarantee.

Why limit the buffer size for the channel?

A channel is a bridge of communication between two goroutines. The code that uses goroutine must have one party providing the data and the other party consuming the data. When the data supply speed of the data side provided by the data is greater than the data processing speed of the consumer, if the channel does not limit the length, the memory will continue to swell until the application crashes. Therefore, limiting the length of the channel is conducive to constraining the supply speed of the data provider. The amount of supplied data must be within the range of the consumer processing volume + the channel length in order to process the data normally.

1.3.5 channel timeout mechanism

The select mechanism is not designed specifically for timeouts, but it can easily solve the timeout problem, because the characteristic of select is that as long as one case has been completed, the program will continue to be executed without considering other cases.

The basic statement is:

Each case statement must be an IO operation.

select {
  case &lt;-chan1:
  // If chan1 successfully reads the data, perform the case processing statement  case chan2 &lt;- 1:
  // If data is successfully written to chan2, the case processing statement is performed  default:
  // If none of the above succeeds, enter the default processing process}

For example, note that the output of 5 nums is because the meaning in select is that the print timeout only after 3s when the ch channel has no value and can be received, that is, the maximum ch channel is blocked and waiting for 3s.

func main() {
  ch := make(chan int)
  quit := make(chan bool)
  //Open a new coroutine  go func() {
    for {
      select {
      case num := &lt;-ch:
        ("num = ", num)
      case &lt;-(3 * ):
        ("time out")
        quit &lt;- true
      }
    }
  }() //Don't forget ()  for i := 0; i &lt; 5; i++ {
    ch &lt;- i
    ()//The main coroutine enters a dormant state, waiting for the above go coroutine to run and enters a blocking waiting state, and then runs back and forth, and communicates through chan  }
  &lt;-quit
  ("Program End")
}
--output--
num = 0
num = 1
num = 2
num = 3
num = 4
time out
Program ends

The above is personal experience. I hope you can give you a reference and I hope you can support me more. If there are any mistakes or no complete considerations, I would like to give you advice.