In-depth understanding of concurrent programming in Go language [30] [Multiplexing]

Article directory


multiplexing

  The operating system-level I/O models are:

  • blocking I/O
  • non-blocking I/O
  • Signal Driven I/O
  • Extra step I/O
  • Multiplexed I/O
      Under Linux, everything is a file. Including ordinary files, directory files, character device files (keyboard, mouse), block device files (hard disk, CD-ROM), sockets, etc. A file descriptor (File descriptor, FD) is an abstract handle for accessing file resources, through which all files are read and written. The file descriptor is a non-negative integer. Each process will open 3 file descriptors by default: 0 standard input, 1 standard output, and 2 standard error. Due to memory constraints, the file descriptor has an upper limit, which can be viewed through ulimit -n, and the file descriptor should be closed in time after it is used up.

blocking I/O

insert image description here

non-blocking I/O

insert image description here

  read and write are blocking mode by default.

ssize_t read(int fd, void *buf, size_t count); 
ssize_t write(int fd, const void *buf, size_t nbytes);

  The file descriptor can be set to non-blocking mode through the system call fcntl.

int flags = fcntl(fd, F_GETFL, 0); 
fcntl(fd, F_SETFL, flags | O_NONBLOCK);

The multiplexed I/O
  select system call can simultaneously monitor the readable or writable status of 1024 file descriptors. poll uses a linked list to store file descriptors, getting rid of the upper limit of 1024. Each operating system implements its own I/O multiplexing functions, such as epoll, evport, and kqueue.

insert image description here

insert image description here

  The go multiplexing function is prefixed with netpoll, and different packages are made for different operating systems to achieve optimal performance. When compiling the go language, a specific branch will be selected for compilation according to the target platform.

insert image description here

  Use the multiplexing of go channel to realize the demo of countdown launch.

insert image description here

package main

import (
	"fmt"
	"os"
	"time"
)

//倒计时
func countDown(countCh chan int, n int, finishCh chan struct{
    
    }) {
    
    
	if n <= 0 {
    
     //从n开始倒数
		return
	}
	ticker := time.NewTicker(1 * time.Second) //创建一个周期性的定时器,每隔1秒执行一次
	for {
    
    
		countCh <- n //把n放入管道
		<-ticker.C   //等1秒钟
		n--          //n减1
		if n <= 0 {
    
      //n减到0时退出
			ticker.Stop()          //停止定时器
			finishCh <- struct{
    
    }{
    
    } //成功结束
			break                  //退出for循环
		}
	}
}

//中止
func abort(ch chan struct{
    
    }) {
    
    
	buffer := make([]byte, 1)
	os.Stdin.Read(buffer) //阻塞式IO,如果标准输入里没数据,该行一直阻塞。注意在键盘上敲完后要按下Enter才会把输入发给Stdin
	ch <- struct{
    
    }{
    
    }
}

func main() {
    
    
	countCh := make(chan int)
	finishCh := make(chan struct{
    
    })
	go countDown(countCh, 10, finishCh) //开一个子协程,去往countCh和finishCh里放数据
	abortCh := make(chan struct{
    
    })
	go abort(abortCh) //开一个子协程,去往abortCh里放数据

LOOP:
	for {
    
     //循环监听
		select {
    
     //同时监听3个channel,谁先准备好就执行谁,然后进入下一次for循环
		case n := <-countCh:
			fmt.Println(n)
		case <-finishCh:
			fmt.Println("finish")
			break LOOP //退出for循环。在使用for select时,单独一个break不能退出for循环
		case <-abortCh:
			fmt.Println("abort")
			break LOOP //退出for循环
		}
	}
}

implementation of timeout

  • ctx, cancel:=context.WithCancel(context.Background()) calling cancel() will close the pipeline corresponding to ctx.Done()

  • ctx,cancel:=context.WithTimeout(context.Background(),time.Microsecond*100) calling cancel or reaching the timeout will close the pipeline corresponding to ctx.Done()

  • The read operation will return immediately after the ctx.Done() pipeline is closed

  Four implementations of function timeout control.

package main

import (
	"context"
	"fmt"
	"time"
)

const (
	WorkUseTime = 500 * time.Millisecond
	Timeout     = 100 * time.Millisecond
)

//模拟一个耗时较长的任务
func LongTimeWork() {
    
    
	time.Sleep(WorkUseTime)
	return
}

//模拟一个接口处理函数
func Handle1() {
    
    
	deadline := make(chan struct{
    
    }, 1)
	workDone := make(chan struct{
    
    }, 1)
	go func() {
    
     //把要控制超时的函数放到一个协程里
		LongTimeWork()
		workDone <- struct{
    
    }{
    
    }
	}()
	go func() {
    
     //把要控制超时的函数放到一个协程里
		time.Sleep(Timeout)
		deadline <- struct{
    
    }{
    
    }
	}()
	select {
    
     //下面的case只执行最早到来的那一个
	case <-workDone:
		fmt.Println("LongTimeWork return")
	case <-deadline:
		fmt.Println("LongTimeWork timeout")
	}
}

//模拟一个接口处理函数
func Handle2() {
    
    
	workDone := make(chan struct{
    
    }, 1)
	go func() {
    
     //把要控制超时的函数放到一个协程里
		LongTimeWork()
		workDone <- struct{
    
    }{
    
    }
	}()
	select {
    
     //下面的case只执行最早到来的那一个
	case <-workDone:
		fmt.Println("LongTimeWork return")
	case <-time.After(Timeout):
		fmt.Println("LongTimeWork timeout")
	}
}

//模拟一个接口处理函数
func Handle3() {
    
    
	//通过显式sleep再调用cancle()来实现对函数的超时控制
	//调用cancel()将关闭ctx.Done()对应的管道
	ctx, cancel := context.WithCancel(context.Background())

	workDone := make(chan struct{
    
    }, 1)
	go func() {
    
     //把要控制超时的函数放到一个协程里
		LongTimeWork()
		workDone <- struct{
    
    }{
    
    }
	}()

	go func() {
    
    
		//100毫秒后调用cancel(),关闭ctx.Done()
		time.Sleep(Timeout)
		cancel()
	}()

	select {
    
     //下面的case只执行最早到来的那一个
	case <-workDone:
		fmt.Println("LongTimeWork return")
	case <-ctx.Done(): //ctx.Done()是一个管道,调用了cancel()都会关闭这个管道,然后读操作就会立即返回
		fmt.Println("LongTimeWork timeout")
	}
}//LongTimeWork timeout

//模拟一个接口处理函数
func Handle4() {
    
    
	//借助于带超时的context来实现对函数的超时控制
	//调用cancel()或到达超时时间都将关闭ctx.Done()对应的管道
	ctx, cancel := context.WithTimeout(context.Background(), Timeout)
	defer cancel() //纯粹出于良好习惯,函数退出前调用cancel()
	workDone := make(chan struct{
    
    }, 1)
	go func() {
    
     //把要控制超时的函数放到一个协程里
		LongTimeWork()
		workDone <- struct{
    
    }{
    
    }
	}()
	select {
    
     //下面的case只执行最早到来的那一个
	case <-workDone:
		fmt.Println("LongTimeWork return")
	case <-ctx.Done(): //ctx.Done()是一个管道,context超时或者调用了cancel()都会关闭这个管道,然后读操作就会立即返回
		fmt.Println("LongTimeWork timeout")
	}
}

func main() {
    
    
	Handle1()
	Handle2()
	Handle3()
	Handle4()
}

Guess you like

Origin blog.csdn.net/m0_52896752/article/details/129798123