I read the implementation of golang channel

Previously, we implemented channel, which is the most popular concurrency toy in recent years, in C ++ for the coming CPU 10000 core era. (I made a library that does things like goroutine/Channel in C ++ 20) At that time, I read the implementation of golang channel, so I will explain the implementation

What is Channel?

Functionally, it's about the same as what is called blocking_queue in thread.

It has the following features -Runs on goroutine (not thread + mutex base) --Use a finite queue --The number of elements in the queue allows 0 --Has a close function

What is goroutine?

I couldn't find the definition of the word goroutine, but I think the word goroutine is used in roughly the following two meanings.

  1. A lightweight golang thread that runs, suspends, and resumes nicely.
  2. Of the golang scheduler, "executable" equivalent to async Task

In addition, goroutine is abbreviated as g in the source code.

reference

Read

Let's take a look at the implementation of channel immediately https://github.com/golang/go/blob/7307e86afda3c5c7f6158d2469c39606fd1dba65/src/runtime/chan.go

Structure definition of channel

https://github.com/golang/go/blob/7307e86afda3c5c7f6158d2469c39606fd1dba65/src/runtime/chan.go#L32

As you read on, it has a structure like the one below.

struct chan{
  std::deque<value_type> value_queue; //It's actually a Ring Buffer, but it's about the same.
  std::list<RecvSudog> recv_queue;
  std::list<SendSudog> send_queue;
  bool closed = false;
  std::mutex mutex;
};

The channel is mainly made up of 3 queues as above value_queue is a queue that stores values send/recv_queue is a queue for storing goroutine in the sending and receiving town.

Generation of chan

https://github.com/golang/go/blob/7307e86afda3c5c7f6158d2469c39606fd1dba65/src/runtime/chan.go#L71

Here, channel is generated When the size of the queue element or element type is 0, we are optimizing to reduce the value_queue, but there is nothing else interesting, so skip it.

Implementation of transmission processing

The behavior of the transmission process is as follows

  1. If the channel is a nil channel or closed, do nothing or die
  2. If there is a goroutine waiting to be received in recv_queue, send the element to that goroutine.
  3. If there is space in value_queue, push the element to value_queue
  4. Enqueue your goroutine to send_queue and interrupt the goroutine

Keeping this in mind and then reading the source will make it easier to read. I think I put it in, so I'll read it right away

Upper: https://github.com/golang/go/blob/7307e86afda3c5c7f6158d2469c39606fd1dba65/src/runtime/chan.go#L158-L200 We are performing processing and optimization that can be implemented only with atomic instructions without using mutex (OS lock).

If there is an element in recv_queue: https://github.com/golang/go/blob/7307e86afda3c5c7f6158d2469c39606fd1dba65/src/runtime/chan.go#L293 Extract one goroutine from recv_queue, pass the value to goroutine, unlock the channel, and make the retrieved goroutine restartable (≒ post to thread_pool)

What should be noted here is that goroutine can be restarted after unlocking. this is This is caused by the problem that "it becomes deadlock when trying to lock the channel when changing the stack size of goroutine". For more information, see issues around here https://github.com/golang/go/issues/12967

If there is space in value_queue: https://github.com/golang/go/blob/7307e86afda3c5c7f6158d2469c39606fd1dba65/src/runtime/chan.go#L214 There is nothing interesting enough to explain, so please read the implementation yourself

When enqueue to the waiting queue: https://github.com/golang/go/blob/7307e86afda3c5c7f6158d2469c39606fd1dba65/src/runtime/chan.go#L258

gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceEvGoBlockSend, 2)

Explain the interruption and lock release by gopark () (https://github.com/golang/go/blob/fd6ba1c8a23d8a3fffb6c475b21f78510152ef5c/src/runtime/proc.go#L319) here

When you call gopark

  1. Suspend the current goroutine
  2. Release the lock outside the goroutine (see the section What is machine: goroutine?)

It becomes the flow.

The reason why we are doing complicated processing such as "opening the lock outside the oroutine (machine)" is This is because if the lock of the channel is released first, the goroutine may be restarted before the goroutine is interrupted by other processing using the channel.

To prevent this, you can add mutex to goroutine so that goroutine is not executed at the same time, but in that case, additional mutex cost is required. It seems that the current implementation is to prevent it

Implementation of reception processing / close

Please read it as it is almost the same as the transmission process.

implementation of select

https://github.com/golang/go/blob/c06a354bccf60ea32ed74238be409a00aac292c5/src/runtime/select.go

select is (for some reason) separated into separate files

The behavior of select is as follows

  1. Perform any immediate action (including nil channel and default)
  2. Enqueue the operation you want to perform on all channels to be selected to the waiting queue (send | recv_queue) and interrupt it.
  3. One of the channels will be executed and goroutine will be restarted
  4. Cancel the enqueued ones that were not executed

Keeping this in mind and then reading the source will make it easier to read. I think I put it in, so I'll read it right away

Determination of poll order and lock acquisition process of each channel: https://github.com/golang/go/blob/c06a354bccf60ea32ed74238be409a00aac292c5/src/runtime/select.go#L121-L230

In what order should pollorder be searched when "if there is something that can be executed immediately, perform that operation"? Is a list that determines As you can see from the implementation, the order is random This is an emulation of asynchronous processing

Regarding the acquisition of lock, the lock is acquired in the order of the memory address of the channel structure. This is because if there are multiple processes to acquire locks for multiple channels, it may become a deadlock unless all of them acquire locks in the same order.

Search for what you can do: https://github.com/golang/go/blob/c06a354bccf60ea32ed74238be409a00aac292c5/src/runtime/select.go#L243

// pass 1 - look for something already waiting

Search for an action that can be performed as described in the comment, and execute it as soon as it is found.

Enqueue to all channels: https://github.com/golang/go/blob/c06a354bccf60ea32ed74238be409a00aac292c5/src/runtime/select.go#L288-L327

// pass 2 - enqueue on all chans

Enqueue to all channels and interrupt

Run/Resume: https://github.com/golang/go/blob/c06a354bccf60ea32ed74238be409a00aac292c5/src/runtime/select.go#L327 Pass3 is executed when restarted by any channel https://github.com/golang/go/blob/7307e86afda3c5c7f6158d2469c39606fd1dba65/src/runtime/chan.go#L819 As a run-time process, a CAS operation is performed on g.selectDone at the time of dequeue to prevent multiple channels from executing the select process.

Post-processing after restart: https://github.com/golang/go/blob/c06a354bccf60ea32ed74238be409a00aac292c5/src/runtime/select.go#L336

// pass 3 - dequeue from unsuccessful chans
// otherwise they stack up on quiet channels
// record the successful case, if any.
// We singly-linked up the SudoGs in lock order.

In pass3, the unused ones that were enqueued in pass2 are deleted from the queue.

in conclusion

If you take a closer look at the golang channel, it's a rather complicated process.

Recommended Posts

I read the implementation of golang channel
I read the implementation of range (Objects / rangeobject.c)
Read the implementation of ARM global timer
I read and implemented the Variants of UKR
I followed the implementation of the du command (first half)
I followed the implementation of the du command (second half)
I read the SHAP paper
I investigated the mechanism of flask-login!
I want to read the html version of "OpenCV-Python Tutorials" OpenCV 3.1 version
[Python] I thoroughly explained the theory and implementation of logistic regression
[Python] I thoroughly explained the theory and implementation of decision trees
I made AI think about the lyrics of Kenshi Yonezu (implementation)
I tried to summarize the frequently used implementation method of pytest-mock
I tried to visualize the common condition of VTuber channel viewers
I checked the contents of docker volume
I tried the asynchronous server of Django 3.0
I checked the options of copyMakeBorder of OpenCV
Othello-From the tic-tac-toe of "Implementation Deep Learning" (3)
I summarized the folder structure of Flask
I didn't know the basics of Python
I see, I will read the UNIX process
The Python project template I think of.
Read all the contents of proc / [pid]
Othello-From the tic-tac-toe of "Implementation Deep Learning" (2)
I tried the pivot table function of pandas
I tried cluster analysis of the weather map
I solved the deepest problem of Hiroshi Yuki.
I checked the list of shortcut keys of Jupyter
I tried to touch the API of ebay
I tried to correct the keystone of the image
Read the output of subprocess.Popen in real time
[Python] Read the source code of Bottle Part 1
Try the free version of Progate [Python I]
I checked the session retention period of django
[Python] I thoroughly explained the theory and implementation of support vector machine (SVM)
I checked the processing speed of numpy one-dimensionalization
I touched some of the new features of Python 3.8 ①
I want to customize the appearance of zabbix
I tried using the image filter of OpenCV
I tried to predict the price of ETF
I tried to vectorize the lyrics of Hinatazaka46!
I took a look at the contents of sklearn (scikit-learn) (1) ~ What about the implementation of CountVectorizer? ~
[Trainer's Recipe] I touched the flame of the Python framework.
Template of python script to read the contents of the file
[GoLang] Set a space at the beginning of the comment
I want to grep the execution result of strace
Play with the UI implementation of Pythonista3 [Super Super Introduction]
I tried to summarize the basic form of GPLVM
I tried the MNIST tutorial for beginners of tensorflow.
Maybe I overestimated the impact of ShellShock on CGI
A python implementation of the Bayesian linear regression class
About testing in the implementation of machine learning models
I checked the output specifications of PyTorch's Bidirectional LSTM
Summarize the knowledge of reading Go's HTTP implementation ~ Slice ~
I checked out the versions of Blender and Python
I want to fully understand the basics of Bokeh
[Golang] Specify an array in the value of map
The performance of PHP was better than I expected
Try to get the contents of Word with Golang
I examined the argument class_weight of Chainer's softmax_cross_entropy function.
I measured the performance of 1 million documents with mongoDB