UNIT 34:: 채널 사용하기
채널: 고루틴끼리 데이터를 주고받고 실행 흐름을 제어하는 기능
- 모든 타입을 채널로 사용 가능
- 채널 자체는 값이 아닌 레퍼런스 타입
채널 사용 형식
(1). make(chan 자료형)
채널을 사용하기 전에는 반드시 make 함수로 공간을 할당해야 함 => 동기 채널이 생성
(:=을 사용하지 않고 var 키워드로 채널을 선언하고 할당할 수도 있음)
채널을 매개변수로 받는 함수는 반드시 go 키워드를 사용하여 고루틴으로 실행해야 함
(2) 변수형 chan 자료형
함수에서 채널을 매개변수로 받을 때는 위와 같은 형식 사용
(3) 채널<-값
채널 변수에는 =로 값을 대입하지 않고 <-연산자를 사용. 위와 같은 예시에서는 sum 함수 안에서 a와 b를 더한 값을 채널 c로 보냄
(4) <-채널
채널 c에서 값을 가져온 뒤 변수 n을 생성해서 대입하는 경우
<-c는 채널에서 값이 들어올 때까지 대기 => 채널에 값이 들어오면 대기를 끝내고 다음 코드 실행
즉, 채널은 값을 주고받는 동시에 동기화 역할까지 수행
34.1 동기 채널
고루틴과 메인 함수를 번갈아 가면서 실행
make 함수처럼 채널을 생성 (이때 매개변수를 하나만 지정했으므로 동기 채널을 생성)
먼저 고루틴을 생성하고 반복문을 실행할 때마다 채널 done에 true 값을 보낸 뒤 1초를 기다림
동기 채널이므로 done에 값을 보내면 다른 쪽에서 값을 꺼낼 때까지 대기
즉 반복문도 실행 X, "고루틴: 숫자"가 출력 X
메인 함수에선 반복문을 실행할 때마다 채널 done 에서 값을 꺼냄
<- done 부분에서 채널에 값이 들어올 때까지 대기-> 먼저 앞의 고루틴에서 done 에 값을 보냈기 때문에 값을 꺼낸 뒤 다음 코드로 진행->고루틴 쪽의 대기도 종료되면서 다시 반복문이 실행된 뒤 채널에 값을 보냄->메인 함수는 채널에서 값을 꺼내고 다시 고루틴도 채널에 값을 보냄
즉 고루틴-> 메인함수->고루틴->메인함수로 실행
34.2 채널 버퍼링
채널의 버퍼가 가득 차면 값을 꺼내서 출력
make(chan 자료형, 버퍼_개수)
채널에 버퍼를 1개 이상 설정하면 비동기 채널이 생성
비동기 채널은 보내는 쪽에서 버퍼가 가득 차면 실행을 멈추고 대기하며 받는 쪽에서는 버퍼에 값이 없으면 대기
고루틴을 생성하고 반복문을 실행할 때마다 채널 done에 true 값을 보냄
비동기 채널이므로 버퍼가 가득 찰 때까지 값을 계속 보냄
채널의 버퍼를 2개로 설정했으므로 done에 true를 2번 보낸 뒤 그 다음 루프에서 대기
메인 함수에서는 반복문을 실행할 때마다 채널 done에서 값을 꺼냄
비동기 채널에 버퍼가 2개이므로 done에는 이미 값이 2개 들어 있음 따라서 루프를 두 번 반복하면서 <-done에서 값을 다시 꺼냄 => 채널이 비어 있으므로 실행을 멈추고 대기 => 다시 고루틴 쪽에서 값을 두 번 보내고 메인 함수에서 두 번 꺼냄
즉 고루틴->고루틴->메인함수-> 메인함수 순서로 실행
34.3 range와 close 사용하기
range 키워드를 사용하여 채널이 닫힐 때까지 반복하면서 값을 꺼낼 수 있음
close키워드를 사용하여 채널을 닫을 수 있음
range와 close 함수의 특징
- 이미 닫힌 채널에 값을 보내면 패닉이 발생
- 채널을 닫으면 range 루프가 종료
- 채널이 열려 있고 값이 들어오지 않는다면 range는 실행되지 않고 계속 대기. 만약 다른 곳에서 채널에 값을 보냈다면 그때부터 range가 계속 반복
34.4 보내기 전용 및 받기 전용 채널 사용하기
보내기 전용 채널과 받기 전용 채널은 흐름이 한 방향으로 고정된 채널
다음은 0부터 4까지 채널에 값을 보내고 다시 채널에서 값을 꺼내서 출력한 뒤 채널에 100을 보낸 뒤 다시 꺼내서 출력하는 코드다 즉
0 1 2 3 4 100 순서대로 실행 결과가 뜨게 된다
보내기 전용: chan <- 자료형 형식
(**값을 보낼 수만 있으며 값을 가져오려고 하면 컴파일 에러 발생)
받기 전용: <-chan 자료형 형식
(**range 키워드 또는 <-채널 형식으로 값을 꺼낼 수만 있으며 값을 보내려고 하면 컴파일 에러 발생)
chan 키워드를 기준으로 <-(화살표)기 붙은 방향을 보면서 보내기 전용인지 받기 전용인지 알 수 있음
34.5 셀렉트 사용하기
select 분기문을 이용하여 여러 채널을 손쉽게 사용할 수 있음
select 분기문은 switch 분기문과 비슷해 보이나 차이점 존재
(1) select 키워드 뒤에 검사할 변수를 따로 지정 X
(2) 각 채널에 값이 들어오면 해당 case가 실행
(3) 보통 select를 계속 처리할 수 있도록 for로 반복
채널 두 개를 생성하고 100밀리초 500밀리 초 간격으로 숫자와 문자열을 보낸 뒤 꺼내서 출력한 코드
...생략
time.After 함수를 사용하면 시간 제한 처리를 할 수 있음
(time.After(): 특정 시간이 지나면 현재 시간을 채널로 보냄)
select 분기문은 채널에 값을 보낼 수도 있음
UNIT 35:: 동기화 객체 사용하기
go 언어는 채널 이외에도 고루틴의 실행 흐름을 제어하는 동기화 객체를 제공
Mutex: 상호 배제라고도 하며 여러 스레드에서 공유되는 데이터를 보호할 때 주로 사용
RWMutex: 읽기/쓰기 뮤텍스. 읽기과 쓰기 동작을 나누어서 잠금을 할 수 있음
Cond: 조건 변수. 대기하고 있는 하나의 객체를 깨울 수도 있고 여러 개를 동시에 깨울 수도 있음
Once: 특정 함수를 딱 한 번만 실행할 때 사용
Pool: 멀티 스레드에서 사용할 수 있는 객체 풀. 자주 사용하는 객체를 풀에 보관했다가 다시 사용
WaitGroup: 고루틴이 모두 끝날 때까지 기다리는 기능
Atomic: 원자적 연산이라고도 하며 더 이상 쪼갤 수 없는 연산이라는 뜻. 멀티 스레드(고루틴), 멀티코어 환경에서 안전하게 값을 연산하는 기능
35.1 뮤텍스 사용하기
고루틴이 공유하는 데이터를 보호할 때 사용
다음은 sync 패키지에서 제공하는 뮤텍스 구조체와 함수
반복 횟수가 많아지면 cpu가 한 개여도 경쟁 조건 발생 혹은 cpu의 코어가 여러 개인 컴퓨터에서는 여러 cpu 코어에서 동시에 공유 데이터에 접근할 수 있으므로 경쟁 조건 발생
이때 data 슬라이스를 뮤텍스로 보호
뮤텍스는 sync.Mutex를 할당한 뒤에 고루틴에서 Lock, Unlock 함수로 사용 보호 시작할 부분에서 Lock 함수를 사용하고 보호 끝낼 부분에서 Unlock 함수를 사용
35.2 읽기, 쓰기 뮤텍스 사용하기
읽기, 쓰기 뮤텍스는 읽기 동작과 쓰기 동작을 나누어 락을 걸 수 있음
- 읽기 락: 읽기 락끼리는 서로를 막지 않음 하지만 읽기 시도 중에 값이 바뀌면 안되므로 쓰기 락은 막습니다
- 쓰기 락: 쓰기 시도 중에 다른 곳에서 이전 값을 읽으면 안 되고 다른 곳에서 값을 바꾸면 안 되므로 읽기 쓰기 락 모두 막음
sync 패키지에서 제공하는 읽기 쓰기 뮤텍스 구조체와 함수
읽기 쓰기 뮤텍스 구조체와 함수를 사용하지 않는다면 중요한 데이터를 쓰고 있는데 다른 곳에서 이전 데이터를 읽는다든지 읽기 시도 중에 값이 바뀐다든지 하는 상황이 생길 수 있음
이를 방지하기 위해서 읽기 동작을 시작할 부분에서 RLock 함수를 사용하고 읽기 동작을 끝낼 부분에 RUnlock 함수를 사용. 이때 짝이 맞지 않으면 데드락이 발생
35.3 조건 변수 사용하기
대기하고 있는 객체 하나만 깨우거나 여러 개를 동시에 깨울 때 사용
조건 변수는 뮤텍스를 먼저 생성한 뒤 sync.NewCond 함수로 생성 대기할 때는 고루틴 안에서 Wait 함수를 사용하며 대기하는 고루틴을 깨울 때는 Signal 함수를 사용 여기서 Wait 함수 부분은 뮤텍스의 Lock, Unloxck 함수로 보호해야 함
대기할 때는 고루틴에서 wait 함수를 사용 대기하고 있는 모든 고루틴을 깨울 때는 Broadcast 함수를 사용
35.4 함수를 한 번만 실행하기
Once를 사용하면 함수를 한 번만 실행할 수 있음
sync 패키지에서 제공하는 Once 구조체와 함수는 다음과 같음
Once를 할당한 뒤에 Do 함수로 사용 Do 함수에는 실행한 함수 이름을 지정 혹은 클로저 형태로 함수 지정 가능
35.5 풀 사용하기
풀은 객체를 상뇽한 뒤에 보관해두었다가 다시 사용하게 해주는 기능
객체를 반복해서 할당하면 메모리 사용량도 늘어나고 메모리를 해제해야 하는 가비지 컬렉터에게도 부담이 됨
풀은 여러 고루틴에서 동시에 사용 가능
풀을 사용하면 메모리 효율적으로 관리 가능. 수명 주기가 짧은 객체는 풀에 적합하지 않음
35.6 대기 그룹 사용하기
대기 그룹은 고루틴이 모두 끝날 때까지 기다릴 때 사용
대기 그룹을 사용하여 고루틴이 끝날 때까지 임시로 대기
add 함수에 설정한 값과 done 함수가 호출되는 값은 같아야 함
또한 done 함수는 defer과 함께 사용하여 지연 호출로도 사용할 수 있음
35.7 원자적 연산 사용하기
원자적 연산: 더이상 쪼갤 수 없는 연산 따라서 여러 스레드, CPU 코어에서 같은 변수를 수정할 때 서로 영향을 받지 않고 안전하게 연산할 수 있음
다음은 sync/atomic 패키지에서 제공하는 원자적 연산의 종류
Add 계열: 변수에 값을 더하고 결과를 리턴
CompareAndSwap 계열: 변수 a와 b를 비교하여 같으면 c를 대입. 그리고 a와 b가 같으면 true, 다르면 false 리턴
Load 계열: 변수에서 값을 가져옴
Store 계열: 변수에 값을 저장
Swap 계열: 변수에 새 값을 대입하고 이전 값을 리턴
UNIT 36:: 리플렉션 사용하기
리플렉션은 실행 시점에 인터페이스나 구조체 등의 타입 정보를 얻어내거나 결정하는 기능
36.1 구조체 태그 가져오기
리플렉션으로 구조체의 태그도 가져올 수 있음
구조체 필드의 태그는 '태그명:"내용"' 형식으로 지정
reflect.TypeOf 함수에 구조체 인스턴스를 넣으면 reflect.Type이 리턴
리턴값은 해당 이름으로 필드가 존재하는지 여부. 필드 정보를 얻은 뒤에 name.Tag.Get("tag1") 과 같이 태그 가져오기 가능
36.2 포인터와 인터페이스의 값 가져오기
포인터는 일반 변수와 다르게 값을 가져오려면 reflect.ValueOf 함수로 값 정보 reflect, Value를 얻어온 뒤 다시 Elem 함수로 값 정보를 가져와야 함. 그리고 변수 타입에 맞는 Int, Float, String 등의 함수로 값을 가져옴
UNIT 37:: 동적으로 함수 생성하기
reflect.MakeFunc 함수를 사용하는 방법
모든 과정을 마친 뒤 hello 함수를 호출하면 Hello world 가 출려고딘다 즉 hello 함수는 h 함수를 이용하여 동적으로 생성된 함수다 하지만 Hello world를 출력하기에는 복잡하기만 한다
UNIT 49:: 파일 처리하기
49.1 파일 쓰기
os 패키지에서 제공하는 파일 함수와 파일 쓰기 함수
49.2 파일 읽기
49.3 파일 읽기 쓰기
os.Openfile 함수는 파일 이름, 플래그, 파일 모드를 받는다
os.O_RDONLY: 읽기 전용으로 파일을 엽니다
os.O_WRONLY: 쓰기 전용으로 파일을 엽니다
os.O_RDONLY: 읽기 쓰기 모두 가능
os.O_APPEND: 파일을 저장했을 때 끝부분에 내용 추가
os.O_CREATE:파일이 없으면 파일 새로 생성
os.O_EXCL: 파일이 없을 때면 새로 생성하여 O_CREATE와 함께 사용
os.O_SYNC: 동기를 사용
os.O_TRUNC: 파일이 있다면 파일을 연 뒤 내용 삭제
49.4 ioutil 패키지 사용
좀 더 간단하게 파일을 읽고 쓸 수 있음
UNIT 50:: 입출력 인터페이스 사용하기
Go언어는 io.Reader과 io.Writer 인터페이스를 활용하여 다양한 방법으로 입출력을 할 수 있음
즉 입출력 인터페이스를 받는 여러 패키지가 있으므로 화면에 출력하는 부분부터 파일 문자열과 같은 인터페이스로 처리 가능
어떤 구조체든 매개변수로 바이트 슬라이스를 받고 정수와 에러값을 리턴하는 read 함수를 가지고 있으면 이러한 인터페이스를 따른다고 할 수 있음 write도 마찬가지임
50.1 파일 처리하기
다음은 bufio 패키지에서 제공하는 입출력 인터페이스 함수
bufio.NewWriter 함수에 file 인스턴스를 넣으면 io.Writer 인스턴스를 따르는 쓰기 인스턴스를 리턴
그리고 WriteString과 같은 메서드로 파일에 문자열을 저장할 수 있음
bufio이므로 파일에 바로 저장하지 않고 임시 공간에 쌓아둠
따라서 버퍼의 내용을 파일에 완전히 저장하려면 Flush 메서드를 사용
매우 큰 파일을 읽을 때는 읽을 크기만큼의 슬라이스를 할당한 뒤 file.Seek 함수로 읽을 위치를 옮겨가면서 일부만 읽으면 됨
50.2 문자열을 파일로 저장하기
strings 패키지에서 제공하는 입출력 인터페이스 함수
bufio 패키지에서 제공하는 입출력 인터페이스 함수
string.NewReader 함수를 사용하면 문자열로 io.Reader 인터페이스를 따르는 읽기 인스턴스를 만들 수 있음 데이터를 가져온 뒤에는 Fluch 메서드를 사용하여 버퍼의 내용을 파일에 저장
50.3 문자열을 화면에 출력
다음과 같이 문자열 io.Reader를 그대로 화면에 출력할 수도 있음
UNIT 51:: JSON 문서 사용하기
프로그래밍 환경이 급격히 인터넷 기반으로 발전하면서 JSON 형식이 널리 쓰이고 있음
다음은 Go 언어에서 JSON을 사용하는 방법들임
다음은 JSON 문서를 읽는 방법이다
51.1 구조체 활용하기
JSON 문서를 구조체를 사용하여 처리
51.2 JSON 파일 사용하기
데이터를 JSON 파일로 저장=>게시물 데이터를 저장할 슬라이스를 선언하고 make 함수로 공간 할당=>데이터를 채워넣은 뒤 json.Marshal 함수를 사용하여 JSON 문서로 변환=>ioutil.WriteFile 함수 사용=>변환된 json 문서를 파일로 저장=>ioutile.ReadFile 함수로 json 파일을 읽어옴=>json.Unmarshal 함수를 사용하여 바이트 슬라이스 b에 저장된 값을 가져옴=>데이터를 JSON 파일로 저장=>게시물 데이터를 저장할 슬라이스를 선언하고 make 함수로 공간 할당=>데이터를 채워넣은 뒤 json.Marshal 함수를 사용하여 JSON 문서로 변환=>ioutil.WriteFile 함수 사용=>변환된 json 문서를 파일로 저장=>ioutile.ReadFile 함수로 json 파일을 읽어 옴=>json.Unmarshal 함수를 사용하여 바이트 슬라이스 b에 저장된 값을 가지고 옴