Go 언어의 동시 프로그래밍에 대한 심층적인 이해 [30] [Multiplexing]

기사 디렉토리


멀티플렉싱

  운영 체제 수준 I/O 모델은 다음과 같습니다.

  • I/O 차단
  • 논블로킹 I/O
  • 신호 기반 I/O
  • 异步I/O
  • 다중 I/O
      Linux에서는 모든 것이 파일입니다. 일반 파일, 디렉토리 파일, 캐릭터 디바이스 파일(키보드, 마우스), 블록 디바이스 파일(하드디스크, CD-ROM), 소켓 등을 포함한다. 파일 설명자(File descriptor, FD)는 모든 파일을 읽고 쓰는 데 사용되는 파일 리소스에 액세스하기 위한 추상 핸들입니다. 파일 설명자는 음수가 아닌 정수입니다. 각 프로세스는 기본적으로 3개의 파일 설명자를 엽니다: 0 표준 입력, 1 표준 출력 및 2 표준 오류. 메모리 제약으로 인해 파일 디스크립터에는 상한이 있으며 이는 ulimit -n을 통해 볼 수 있으며 파일 디스크립터는 다 사용한 후 제 시간에 닫아야 합니다.

I/O 차단

여기에 이미지 설명 삽입

논블로킹 I/O

여기에 이미지 설명 삽입

  읽기 및 쓰기는 기본적으로 차단 모드입니다.

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

  파일 설명자는 시스템 호출 fcntl을 통해 비차단 모드로 설정할 수 있습니다.

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

다중화 I/O
  선택 시스템 호출은 1024 파일 디스크립터의 읽기 가능 또는 쓰기 가능 상태를 동시에 모니터링할 수 있습니다. poll은 연결 목록을 사용하여 파일 설명자를 저장하여 1024의 상한선을 제거합니다. 각 운영 체제는 epoll, evport 및 kqueue와 같은 자체 I/O 다중화 기능을 구현합니다.

여기에 이미지 설명 삽입

여기에 이미지 설명 삽입

  Go 다중화 기능에는 netpoll이 접두사로 붙으며 최적의 성능을 달성하기 위해 운영 체제마다 다른 패키지가 만들어집니다. Go 언어를 컴파일할 때 대상 플랫폼에 따라 컴파일을 위해 특정 분기가 선택됩니다.

여기에 이미지 설명 삽입

  카운트 다운 시작 데모를 실현하기 위해 이동 채널의 다중화를 사용하십시오.

여기에 이미지 설명 삽입

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循环
		}
	}
}

제한 시간 구현

  • ctx, cancel:=context.WithCancel(context.Background()) 호출 cancel()은 ctx.Done()에 해당하는 파이프라인을 닫습니다.

  • ctx,cancel:=context.WithTimeout(context.Background(),time.Microsecond*100) 취소를 호출하거나 시간 초과에 도달하면 ctx.Done()에 해당하는 파이프라인이 닫힙니다.

  • 읽기 작업은 ctx.Done() 파이프라인이 닫힌 직후에 반환됩니다.

  기능 제한 시간 제어의 네 가지 구현.

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()
}

Supongo que te gusta

Origin blog.csdn.net/m0_52896752/article/details/129798123
Recomendado
Clasificación