Recent Comments
Link
Recent Posts
Today
Total
«   2025/03   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
관리 메뉴

Study Memory Work

[Go Lang] 채널 Channel 본문

Programing/Go

[Go Lang] 채널 Channel

Hera Choi 2023. 3. 6. 17:11
func main() {
	ch1 := make(chan int)
	ch2 := make(chan string)

	go func() {
		for { // 채널이 닫힐 때 까지 반복
			ch1 <- 77
			time.Sleep(250 * time.Millisecond)
		}
	}()
	go func() {
		for { // 채널이 닫힐 때 까지 반복
			ch2 <- "HIHI"
			time.Sleep(500 * time.Millisecond)
		}
	}()

	go func() {
		for {
			select {
			case num := <-ch1:
				fmt.Println("ch1 : ", num)
			case str := <-ch2:
				fmt.Println("ch2 : ", str)
				// default:				// 타이밍 문제로 채널을 이용해 select문을 사용할 때는 default 사용하지 말 것!
				// 	fmt.Println("default test")
			}

		}
	}()

	time.Sleep(7 * time.Second)		// 7초 뒤에 main 종료

}

Channel

  • 고루틴간의 상호 정보(데이터) 교환실행 흐름 동기화(동기식)를 위해 사용 
    - 수신이 완료될 때까지 대기하고, 데이터가 수신이 되면 진행한다.
  • 실행 흐름을 제어 가능(동기, 비동기 모두) -> 일반 변수로 선언하려 사용
  • 데이터 전달 자료형 선언 후 사용해야 함.(지정된 타입만 주고받을 수 있음)
  • interface{} 전달을 통해 자료형 상관없이 전송 및 수신 가능
  • 값을 전달(복사 후 : bool, int 들) ,  포인터(슬라이즈, 맵) 등을 전달할 때는 주의
  • 멀티프로세싱 처리에서 교착상태(경쟁) 주의
  • '<-', '->' 사용.
  • '채널 ->' : 수신
  • '채널 <- 데이터' : 송신

 

채널 선언

	// 1.
	var c chan int
	c = make(chan int)

	// 2.
	v := make(chan int)

 

채널 - 동기화 예제

func work11(v chan int) {
	fmt.Println("Work1 : Start --->", time.Now())
	time.Sleep(1 * time.Second)
	fmt.Println("Work1 : End --->", time.Now())
	v <- 1
}

func work22(v chan int) {
	fmt.Println("Work2 : Start --->", time.Now())
	time.Sleep(1 * time.Second)
	fmt.Println("Work2 : End --->", time.Now())
	v <- 2
}

func main() {

	fmt.Println("Main : Start ---> ", time.Now())

	v := make(chan int)
	go work11(v)
	go work22(v)

	<-v
	<-v		// 이 두 채널이 마지막까지 모든 데이터의 수신을 대기하기 때문에 main thread가 먼저 끝날일 없음
	fmt.Println("Main : End ---> ", time.Now())

}
출력 : 
(time.Sleep을 쓰지 않아도 main이 고루틴이 다 돈 후 종료된 것을 볼 수 있다.) 

Main : Start --->  2023-03-06 13:53:19.398981 +0900 KST m=+0.000235168
Work2 : Start ---> 2023-03-06 13:53:19.399308 +0900 KST m=+0.000562209
Work1 : Start ---> 2023-03-06 13:53:19.399313 +0900 KST m=+0.000567376
Work1 : End ---> 2023-03-06 13:53:20.400448 +0900 KST m=+1.001706418
Work2 : End ---> 2023-03-06 13:53:20.400424 +0900 KST m=+1.001682626
Main : End --->  2023-03-06 13:53:20.400547 +0900 KST m=+1.001804876

 

채널 Buffer

  • 버퍼 : 양에 제한 없이 계속 데이터를 보낼 수 있고, chan을 생성할 때 그 범위를 정할 수 있다.
	ch := make(chan bool, 4)	// 4개가 가득찰 때까지 받고, 가득차면 비운다
  • 송신 측 : 버퍼가 가득찰 때까지 계속 보냄. 가득차면 대기
  • 수신 측 : 버퍼가 빌 때 까지 계속 데이터를 받음. 비어있으면 대기
func main() {

	runtime.GOMAXPROCS(1)    // 갯수 지정
	ch := make(chan bool, 4) // 채널 버퍼의 크기 지정
	cnt := 20

	go func() {
		for i := 0; i < cnt; i++ {
			ch <- true		// 발신
			fmt.Println("Go : ", i)
		}
	}()

	for i := 0; i < cnt; i++ {
		<- ch			// 수신
		fmt.Println("Main : ", i)
	}

}
출력 : 
( 지정한 버퍼의 갯수만큼 보내고, 비우고를 반복하는 것을 볼 수 있다.)

Go :  0
Go :  1
Go :  2
Go :  3
Go :  4
Main :  0
Main :  1
Main :  2
Main :  3
Main :  4
Main :  5
Go :  5
Go :  6
Go :  7
Go :  8
Go :  9
Go :  10
Main :  6
Main :  7
Main :  8

...

 

채널 Close

  • 채널은 사용 후 닫고 반환해주어야 함. 그렇지 않으면 over됨
  • range가 있을 경우 close하지 않으면 무한 대기한다.
func main() {

	ch := make(chan bool)
	go func() {
		for i := 0; i < 5; i++ {
			ch <- true
		}
		close(ch)
	}()
	
    // range ch --> ch 채널이 닫힐 때까지 수신 대기한다!
	for i := range ch {
		fmt.Println("ex1 : ", i)
	}
    
}

 

수신전용 채널, 발신전용 채널

  • Go는 수신 전용 채널과 발신 전용 채널을 구분한다
  • 발신 전용 : channel <- 데이터형
  • 수신 전용 : <- channel
  • 매개변수를 통해서 전용 채널이 무엇인 지 확인할 수 있다.
  • 채널 또한 함수의 반환 값으로 사용이 가능하다
// 발신전용 표시
func sendOlny(c chan<- int, cnt int) {
	for i := 0; i < 10; i++ {
		c <- i
	}

	c <- 777

	//  fmt.Println(<-c)    -> 발신전용에 수신하려고 하면 오류남!!
}

// 수신전용 표시
func receiveOnly(c <-chan int) {
	for i := range c {
		fmt.Println("received : ", i)
	}

}

func main() {

	c := make(chan int)

	go sendOlny(c, 10) // 발신전용
	go receiveOnly(c)  // 수신전용

	time.Sleep(time.Second * 2)

}

 

채널을 Return 값으로 사용하기

func sum(cnt int) <-chan int {		// 채널을 return 값으로 설정
	sum := 0
	tot := make(chan int)
	go func() {
		for i := 1; i < cnt; i++ {
			sum += i
		}	
		tot <- sum			// 전체 sum값을 채널에 발신
	}()

	return tot // 데이터를 발신한 상태의 채널을 return 값으로 보냄
}

func main() {

	//1
	c := sum(100)

	fmt.Println("ex1 : ", <-c)
}
출력 : 

ex1 :  4950

 

func receiveOnly(cnt int) <-chan int {
	sum := 0
	tot := make(chan int)

	go func() {
		for i := 0; i < cnt; i++ {
			sum += i
		}
		tot <- sum
		tot <- 777
		tot <- 777

		close(tot) // 닫아버렸기 때문에 tot 채널은 값만 들어있는 수신 전용 채널이 됨.
	}()

	return tot	// 수신 전용 채널 반환
}

// 수신 전용 메소드를 파라미터로 받고 return도 하는 메소드
func total(c <-chan int) <-chan int {
	tot := make(chan int)

	go func() {
		a := 0
		for i := range c {
			a += i
		}

		tot <- a
		close(tot)		// tot에 a를 담고 닫아버려 수신전용 채널로 만듬
	}()

	return tot
}


func main() {

	cc := receiveOnly(100) //채널 반환
	output := total(cc)    //채널 전달 후 반환

	//  output<- 10 --> Error!!!
	fmt.Println("ex2 : ", <-output)

}
출력 : 

ex2 :  6504

 

channel select 구문

  • channel에서 값이 수신되면 case 문 실행.
func main() {
	ch1 := make(chan int)
	ch2 := make(chan string)

	go func() {
		for { // 채널이 닫힐 때 까지 반복
			ch1 <- 77
			time.Sleep(250 * time.Millisecond)
		}
	}()
	go func() {
		for { // 채널이 닫힐 때 까지 반복
			ch2 <- "HIHI"
			time.Sleep(500 * time.Millisecond)
		}
	}()

	go func() {
		for {
			select {
			case num := <-ch1:
				fmt.Println("ch1 : ", num)
			case str := <-ch2:
				fmt.Println("ch2 : ", str)
				// default:	타이밍 문제로 채널을 이용해 select문을 사용할 때는 default 사용하지 말 것!
				// 	fmt.Println("default test")
			}

		}
	}()

	time.Sleep(7 * time.Second) // 7초 뒤에 main 종료

}
출력:

ch1 :  77
ch2 :  HIHI
ch1 :  77
ch1 :  77
ch2 :  HIHI
ch1 :  77
ch1 :  77
ch2 :  HIHI
ch1 :  77
ch2 :  HIHI
ch1 :  77
ch1 :  77
ch2 :  HIHI
ch1 :  77

... (7초간 진행)