- 컨텍스트 : 고루틴에 작업을 요청할 때 작업 취소나 작업 시간 등을 설정할 수 있는 작업 명세서 역할
새로운 고루틴으로 작업을 시작할 때 일정 시간 동안만 작업을 지시하거나 외부에서 작업을 취소할 때 사용한다
또한 작업 설정에 관한 데이터를 전달할 수도 있다
작업 취소가 가능한 컨텍스트
// 작업이 취소될 때까지 1초마다 메시지를 출력하는 고루틴
package main
import (
"context"
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func main() {
wg.Add(1)
ctx, cancel := context.WithCancel(context.Background()) // 1. 컨텍스트 생성
go PrintEverySecond(ctx)
time.Sleep(5 * time.Second)
cancel()
wg.Wait()
}
func PrintEverySecond(ctx context.Context) {
tick := time.Tick(time.Second)
for {
select {
case <-ctx.Done():
wg.Done()
return
case <-tick:
fmt.Println("Tick")
}
}
}
// Tick 5번 출력
작업 시간을 설정한 컨텍스트
// 일정 시간동안만 작업을 지시할 수 있는 컨텍스트
package main
import (
"context"
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func main() {
wg.Add(1)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) // 1. 컨텍스트 생성
go PrintEverySecond(ctx)
time.Sleep(5 * time.Second)
cancel()
wg.Wait()
}
func PrintEverySecond(ctx context.Context) {
tick := time.Tick(time.Second)
for {
select {
case <-ctx.Done():
wg.Done()
return
case <-tick:
fmt.Println("Tick")
}
}
}
// Tick 3번 출력
특정 값을 설정한 컨텍스트
// 컨텍스트에 특정 키로 값을 읽어올 수 있도록 설정할 수 있다
package main
import (
"context"
"fmt"
"sync"
)
var wg sync.WaitGroup
func main() {
wg.Add(1)
ctx := context.WithValue(context.Background(), "number", 9) // 1. 컨텍스트 생성 (값 추가)
ctx = context.WithValue(ctx, "float", 5.0)
ctx = context.WithValue(ctx, "keyword", "Lilly")
go print(ctx)
wg.Wait()
}
func print(ctx context.Context) {
if v := ctx.Value("number"); v != nil { // 2. 컨텍스트에서 값 읽기
n := v.(int)
fmt.Println("number:", n*n)
}
if v := ctx.Value("float"); v != nil { // 3. 컨텍스트에서 값 읽기
n := v.(float64)
fmt.Println("float:", n*n)
}
if v := ctx.Value("keyword"); v != nil { // 4. 컨텍스트에서 값 읽기
n := v.(string)
fmt.Println("keyword:", n)
}
wg.Done()
}
number: 81
float: 25
keyword: Lilly
┏ 채널 타입 ┏ 메시지 타입
var messages chan string = make(chan string)
┗ 채널 인스턴스 변수 ┗ 채널 키워드
- make() 를 통해 생성한다
- 채널 타입은 channel을 의미하는 chan과 메시지 타입을 합쳐서 표현한다
- chan string은 string 타입 메시지를 전달하는 채널의 타입이다
채널에 데이터 넣기
┏ 채널 인스턴스 ┏ 넣을 데이터
messages <- "This is a message"
┗ 연산자
채널에서 데이터 빼기
┏ 빼낸 데이터를 담을 변수 ┏ 채널 인스턴스
var msg string <- messages ┛
┗ 연산자
채널에서 데이터를 하나 넣고 빼는 예제
package main
import (
"fmt"
"sync"
"time"
)
func main()
{
var wg sync.WaitGroup
ch := make(chan int) // 채널 생성
wg.Add(1)
go square(&wg, ch) // 고루틴 생성
ch <- 9 // 채널에 데이터 넣음
wg.Wait() // 작업이 완료되때까지 대기
}
func square(wg *sync.WaitGroup, ch chan int)
{
n := <- ch // 데이터를 빼옴
// 여기서 데이터가 수신될때까지 대기
fmt.Printf("Square: %d \n", n*n) // Output Square: 81
wg.Done() // 작업 완료를 알림
}
채널의 크기
- 일반적으로 채널을 생성하면 크기가 0인 채널이 만들어진다. 크기가 0이라는 뜻은 채널에 들어온 데이터를 담아둘 곳이 없다는 얘기가 된다
- 채널의 크기가 0이면 데이터 수신자가 없으면 데이터 송신자가 대기를 한다
- 채널의 크기가 존재하면 데이터 송신자는 데이터를 넣고 원래의 동작을 수행한다
- 채널의 크기가 존재하지만 채널의 크기를 초과한 데이터가 송신되면 담아둘 곳이 없어서 데이터 송신자는 멈추게 된다
채널에서 데이터를 가져가지 않아서 프로그램이 멈추는 예제
package main
import "fmt"
func main() {
ch := make(chan int) // 크기가 0인 채널 생성
ch <- 9 // 데이터 수신자가 없기 때문에 여기서 멈춤
fmt.Println("Never print") // 실행되지 않는다.
}
크기를 가지는 채널 생성 예제
package main
import f "fmt"
func main() {
f.Println("beffered")
messages := make(chan string, 2) // string 타입의 채널을 만들고 해당 채널의 버퍼는 2이다.
messages <- "wisoft"
messages <- "lab"
f.Println(<-messages)
f.Println(<-messages)
}
make의 두번째 인자에 숫자를 넣어주면 그 크기로 생성된다
채널의 크기를 넘어가는 경우엔?
package main
import f "fmt"
func main() {
f.Println("beffered")
messages := make(chan string, 2)
messages <- "wisoft"
messages <- "lab"
messages <- "golang" // 여기서 deadlock 발생
f.Println(<-messages)
f.Println(<-messages)
f.Println(<-messages)
}
------------ 아래 부터는 영역을 나누는 방법 ------------
채널에서 데이터 대기
package main
import (
"fmt"
"sync"
)
func square(wg *sync.WaitGroup, ch chan int) {
for n := range ch {
fmt.Println("Square: %d \n", n*n)// 2. 데이터를 계속 기다림
}
wg.Done() // 4. 실행되지 않음
}
func main() {
var wg sync.WaitGroup
ch := make(chan int)
wg.Add(1)
go square(&wg, ch)
for i := 0; i < 10; i++ {
ch <- i * 2 // 1. 데이터 송신
}
wg.Wait() // 3. 작업 완료를 기다림
}
위에 코드에서는 for range 구문에서 계속해서 데이터가 들어오기를 기다리기 때문에 4번이 실행되지 않고 deadlock
채널 닫기
package main
import (
"fmt"
"sync"
)
func square(wg *sync.WaitGroup, ch chan int) {
for n := range ch {
fmt.Println("Square: %d \n", n*n)
}
wg.Done()
}
func main() {
var wg sync.WaitGroup
ch := make(chan int)
wg.Add(1)
go square(&wg, ch)
for i := 0; i < 10; i++ {
ch <- i * 2
}
close(ch) // 채널 닫음
wg.Wait()
}
데이터를 모두 넣고 채널을 닫아주면 for range에서는 채널이 닫혀서 더이상 수신을 할 수 없기 때문에 for문을 빠져나오고 정상 종료된다
Select 문
Select문은 여러 채널을 동시에 기다릴 수 있다. 하지만 어떤 채널에서라도 데이터를 읽어오면 해당 구문을 실행하고 select문은 종료된다. 하나의 case만 처리되면 종료되기 때문에 반복해서 데이터를 처리하고 싶다면 for문과 함께 사용해야 한다
select {
case n := <-ch1
... // ch1 채널에서 데이터를 빼낼 수 있을 때 실행
case n := <-ch2
... // ch2 채널에서 데이터를 빼낼 수 있을 때 실행
case ...
}
select를 이용한 데이터 읽고 처리하는 예제
package main
import (
"fmt"
"sync"
)
func square(wg *sync.WaitGroup, ch chan int, quit chan bool) {
for {
select { // ch와 quit 양쪽을 모두 기다림 (non blocking)
case n := <-ch:
fmt.Println("Square: %d \n", n*n)
case <-quit:
wg.Done()
return
default:
fmt.Println("test") // 실행되지 않음
}
}
}
func main() {
var wg sync.WaitGroup
ch := make(chan int)
quit := make(chan bool)
wg.Add(1)
go square(&wg, ch, quit)
for i := 0; i < 10; i++ {
ch <- i * 2
}
quit <- true
wg.Wait()
}
------------ 아래 부터는 역할을 나누는 방법------------
자동차 공장에서 자동차를 만드는 예제
package main
import (
"fmt"
"sync"
"time"
)
type Car struct {
Body string
Tire string
Color string
}
var wg sync.WaitGroup
var startTime = time.Now()
func main() {
tireCh := make(chan *Car)
paintCh := make(chan *Car)
fmt.Printf("Start Factory \n")
wg.Add(3)
go MakeBody(tireCh) // 1. 고루틴 생성
go InstallTire(tireCh, paintCh)
go PaintCar(paintCh)
wg.Wait()
fmt.Println("Close the factory")
}
func MakeBody(tireCh chan *Car) { // 2. 차체 생산
tick := time.Tick(time.Second)
after := time.After(10 * time.Second)
for {
select {
case <-tick:
// Make a Body
car := &Car{}
car.Body = "Sports car"
tireCh <- car
case <-after: // 3. 10초 뒤 종료
close(tireCh)
wg.Done()
return
}
}
}
func InstallTire(tireCh chan *Car, paintCh chan *Car) { // 4. 바퀴 설치
for car := range tireCh {
// Make Tire
time.Sleep(time.Second)
car.Tire = "Winter tire"
paintCh <- car
}
wg.Done()
close(paintCh)
}
func PaintCar(paintCh chan *Car) { // 5. 도색
for car := range paintCh {
// Make a body
time.Sleep(time.Second)
car.Color = "Red"
duration := time.Now().Sub(startTime) // 6. 경과 시간 출력
fmt.Printf("%.2f Complete Car: %s %s %s\n",
duration.Seconds(), car.Body, car.Tire, car.Color)
}
wg.Done()
}
slice := []int{1, 2, 3, 4, 5, 6}
slice = append(slice, 0) // 맨 뒤에 요소 추가
idx := 2 // 추가하려는 위치
for i := len(slice)-2; i <= idx; i--
{
slice[i+1] = slice[i] // 맨 뒤부터 추가하려는 위치까지 값을 하나씩 밀기
}
slice[idx] = 100 // 값 변경
slice1 := []int{1, 2, 3, 4, 5}
slice2 := make([]int, len(slice1)) // slice1과 같은 길이의 슬라이스 생성
for i, v := range slice1
{
slice2[i] = v // slice1의 모든 요소값 복사
}
slice1[1] = 100 // slice1 [1, 100, 3, 4, 5]
// slice2 [1, 2, 3, 4, 5]
append를 이용한 슬라이스 복제
slice1 := []int{1, 2, 3, 4, 5}
// append를 이용한 slice1 복사
slice2 := append([]int{}, slice1[0], slice1[1], slice1[2], slice1[3], slice1[4])
copy를 이용한 슬라이스 복사
slice1 := []int{1, 2, 3, 4, 5}
slice2 := make([]int, len(slice1)) // slice1과 같은 길이의 슬라이스 생성
copy(slice2, slice1)