사용 교재 : 가장 빨리 만나는 Go언어
3주차 : UNIT 35 ~ 51
[UNIT 35] 동기화 객체 사용하기
- 동기화 객체
> Mutex : 상호 배제라고도 하며 여러 스레드에서 공유되는 데이터를 보호할 때 사용
> RWMutex : 읽기/쓰기 뮤텍스., 읽기와 쓰기 동작을 나누어 잠금을 걸 수 있다
> Cond : 조건 변수, 대기하고 있는 하나의 객체를 깨울 수 있고 여러 개를 동시에 깨울 수 있다
> Pool : 멀티 스레드에서 사용할 수 있는 객체, 자주 사용하는 객체를 품에 보관했다 다시 사용한다
> WaitGroup : 고루틴이 모두 끝날 때까지 기다리는 기능
> Atomic : 원자적 연산, 더 이상 쪼갤 수 없는 연산이라는 뜻으로 멀티 스레드, 멀티 코어 환경에서 안전하게 값을 연산하는 기능
1. 뮤텍스 사용하기
- 여러 고루틴이 공유하는 데이터를 보호할 때 사용하며 sync 패키지에서 제공하는 뮤텍스 구조체와 함수
> sync.Mutex
> func (m *Mutex) Lock() : 뮤텍스 잠금
> func (m *Mutex) Unlock() : 뮤텍스 잠금 해제
- 고루틴 두 개에서 각각 1000번씩 슬라이스에 값을 추가하기
> data에 1을 2000번 추가했기 때문에 길이는 2000이 되어야 하는데 그렇지 않음
> 두 고루틴이 동시에 data에 접근했기 때문에 append 함수가 일일히 정확하게 처리되지 않음 -> 경쟁 조건이라 한다
> runtime.Gosched( ) : 다른 고루틴이 CPU를 사용할 수 있도록 양보한다
(time.Sleep( )보다 runtime.Gosched( )가 더 명확함)
* CPU 코어가 한 개였다면 경쟁 조건 상황이 발생하지 않고 정확하게 2000이 출력될 수 있다. 하지만 반복 횟수가 많아지면 CPU 코어가 하나라도 경쟁 조건이 발생한다. 또한, CPU 코어를 여러 개 사용하면 동시에 공유 데이터에 접근할 수 있어 경쟁 조건이 발생한다.
- data 슬라이스를 뮤텍스로 보호
> 뮤텍스는 sync.Mutex를 할당하고 고루틴에서 Lock, Unlock 함수로 사용
> 보호를 시작하는 부분에서 Lock( )을 사용
> 보호를 끝낼 부분에서 Unlock( )을 사용
> Lock과 Unlock의 짝이 맞춰주어야 하며 맞지 않으면 데드락이 발생한다
> 각자의 슬라이스를 보호하며 실행하기 때문에 정확히 2000이 출력된다
2. 읽기, 쓰기 뮤텍스 사용하기
- 읽기, 쓰기 뮤텍스는 읽기 동작과 쓰기 동작을 나눠 잠금을 걸 수 있다
> 읽기 락 : 읽기 락끼리는 서로 막지 않는다. But, 읽기 시도 중에 값이 바뀌면 안 되므로 쓰기 락은 막는다
> 쓰기 락 : 쓰기 시도 중 다른 곳에서 다른 곳에서 이전 값을 읽으면 안 되고, 다른 곳에서 값을 바꾸면 안 되므로 읽기, 쓰기 락을 모두 막는다
- sync 패키지에서 제공하는 읽기, 쓰기 뮤텍스 구조체와 함수
> sync.RwMutex
> func (rw *RWMutex) Lock( ), func (rw *RWMutex) Unlock( ) : 쓰기 뮤텍스 잠금, 잠금 해제
> func (rw *RWMutex) RLock( ), func (rw *RWMutex) RUnlock( ) : 읽기 뮤텍스 잠금 및 해제
- 읽기 쓰기 뮤텍스를 사용하지 않고 고루틴에서 값 출력하기
> 값을 쓰는 고루틴은 1개, 값을 읽는 고루틴은 2개를 생성한다
> 아래 예제는 간단한 상황이라 큰 문제가 없지만 중요한 데이터를 쓸 때는 다른 곳에서 이전 데이터를 읽거나 읽기 시도 중 값이 바뀌는 문제가 생길 수 있다
- 읽기, 쓰기 뮤텍스 사용해 읽기, 쓰기 동장 실행이 보장되도록 하기
> 읽기 동작이 시작할 부분에 RLock 함수를, 읽기 동작을 끝낼 부분에 RUlock 함수를 사용한다
> 쓰기 동작을 시작할 부분에 Lock 함수를, 쓰기 동작을 끝낼 부분에 Unlock 함수를 사용한다
> RLcok, RUnlock, Lock, Unlock 함수는 반드시 짝이 맞아야 하고 아니라면 데드락이 발생한다
> 읽기 동작은 서로 막지 않고 항상 동시에 실행
* 읽기 2번 쓰기 1번 순서로 진행된다
- 읽기, 쓰기 뮤텍스는 중요한 쓰기 작업을 할 때 다른 곳에서 이전 데이터를 읽지 못하게 방지하거나 읽기 작업을 할 때 데이터가 바뀌는 상황을 방지해준다
- 읽기, 쓰기 뮤텍스는 쓰기 동작보다 읽기 동작이 많을 때 유리함
3. 조건 변수 사용하기
- sync 패키지에서 제공하는 조건 변수의 함수
> sync.Cond
> func NewCond(l Locker) *Cond : 조건 변수 생성
> func (c *Cond) Wait( ) : 고루틴 실행을 멈추고 대기
> func (c *Cond) Signal( ) : 대기하고 있는 고루틴 하나만 깨움
> func (c *Cond) Broadcast( ) : 대기하고 있는 모든 고루틴 깨움
- 대기하고 있는 고루틴 하나씩 깨우기
- 대기하고 있는 모든 고루틴 깨우기
4. 함수를 한 번만 실행하기
- Once를 사용하면 함수를 한 번만 실행할 수 있다
- Once는 한 번만 실행되는 특성이 있어 복잡한 반복문 안에서 각종 초기화에 유용하다
- sync 패키지에서 제공하는 Once의 구조체 함수
> sync.Once
> func (*Once) Do(f func()) : 함수를 한 번만 실행
- hello, world 한 번만 실행되게 하기
> Once를 할당 한 뒤 Do 함수로 실행할 함수를 지정한다
5. 풀 사용하기
- 풀은 객체를 사용한 후 보관해 두었다 다시 사용하게 해주는 기능이다
- 풀은 캐시라고도 할 수 있고 메모리 할당과 해제 횟수를 줄여 성능을 높이고자 할 때 사용한다
- 풀은 고루틴에서 동시에 사용할 수 있다
- sync 패키지에서 제공하는 풀의 구조체와 함수
> sync.Pool
> func (p *Pool) Get() interface{} : 풀에 보관된 객체를 가져옴
> func (p *Pool) Put(x interface{}) : 풀에 객체를 보관
- 풀을 이용해 정수 10개짜리 슬라이스 공유
> 첫 번째 고루틴에서 슬라이스에 랜덤한 숫자 10개 저장해 출력
> 두 번째 고루틴에서 짝수 10개를 저장해 출력
6. 대기 그룹 사용하기
- 대기 그룹은 고루틴이 모두 끝날 때까지 기다리고 사용
- sync 패키지에서 제공하는 대기 그룹의 구조체와 함수
> sync.WaitGroup
> func (wg *WaitGroup) Add(delta int) : 대기 그룹에 고루틴 개수 추가
> func (wg *WaitGroup) Done() : 고루틴이 끝났다는 것을 알려줄 때 사용
> func (wg *WaitGroup) Wait() : 모든 고루틴이 끝날 때까지 기다림
> Add 함수에 설정한 값과 Done 함수가 호출되는 횟수는 같아야 함
> Done 함수는 defer와 함께 사용해 지연 호출로 사용할 수 있다
7. 원자적 연산 사용하기
- 원자적 연산이란 더 이상 쪼갤 수 없는 연산이라는 뜻이다
- 보통 원자적 연산은 CPU 명령어를 직접 사용하여 구현되어 있다
- 고루틴 사용해 정수형 변수를 2000번 더하고, 1000번 빼기
> 예상 결과는 1000이지만 결과는 그렇지 않음 -> 여러 변수에 고루틴이 동시에 접근하면서 정확하게 연산 되지x
- 원자적 연산을 사용해 계산하기
> 정확하게 1000이 출력됨
- sync/atomic 패키지에서 제공하는 원자적 연산자
> Add 계열 : 변수에 값을 더하고 결과를 리턴한다
> CompareAndSwap 계열 : 변수 A와 B를 비교해 같으면 C를 대입하고 true를 리턴하고 다르면 false를 리턴한다
> Load 게열 : 변수에서 값을 가져온다
> Store 계열 : 변수에서 값을 저장한다
> Swap 계열 : 변수에 새 값을 대입하고, 이전 값을 리턴한다
[UNIT 36] 리플렉션 사용하기 (pg. 213)
- 리플렉션 : 실행 시점에 인터페이스나 구조체 등의 타입 정보를 얻어내거나 결정하는 기능
- Java, C#처럼 가상 머신 위에서 실행되는 언어나 Python, Ruby 등의 스크립트 언어에서 주로 사용하였다
- 변수와 구조체의 타입을 표시하기
- 리플렉션으로 변수의 타입과 값에 대한 상세한 정보도 얻어올 수 있다
1. 구조체 태그 가져오기
- 리플렉션으로 구조체의 태그 가져오기
> 구조체 필드의 태그는 `(백쿼트)태그명:”내용”` 형식으로 지정한다
> 구조체 필드에 태그를 여러 개 지정할 때는 공백으로 구분해준다
> reflect.TypeOf 함수에 구조체 인스턴스를 넣으면 reflect.Type이 리턴된다
> Get 함수를 이용해 태그를 가져올 수 있다
2. 포인터와 인터페이스의 값 가져오기
- 일반 포인터와 인터페이스의 값을 가져오는 방법
> 포인터는 일반 변수와 다르게 값을 가져오기 위해선 reflect.ValueOf 함수로 값 정보를 가져와 Elem 함수로 값을 가져와야 한다
> 빈 인터페이스 b에 1을 대입해 int 타입이지만 b는 인터페이스이기 때문에 컴파일 에러가 발생한다
* 인터페이스 값을 가져올 때 변수 타입에 맞는 Int, Float, String 등의 함수를 사용해선 안 된다
[UNIT 37] 동적으로 함수 생성하기
- 리플렉션을 사용하여 동적으로 함수를 만들어 내기
> reflect.MakeFunc 함수를 사용하기
- 정수, 실수, 문자열 더하기를 처리하는 함수 만들기
[UNIT 49] 파일 처리하기
1. 파일 쓰기
- os 패키지에서 제공하는 파일 함수와 파일 쓰기 함수
> func Create(name string) (file *File, err error) : 기존 파일을 열거나 새 파일을 생성
> func (F *File) Close() error : 열린 파일을 닫음
> func (f *File) Write(b []byte) (n int, err error) : 파일에 값을 씀, 파일에 쓴 데이터의 길이와 에러 값을 리턴
- hello.txt 파일에 “Hello world” 문자열 저장
2. 파일 읽기
- os 패키지에서 제공하는 파일 열기, 파일 정보, 파일 읽기 함수
> func Open(name string) (file *File, err error) : 파일 열기
> func (f * File) Stat() (fi FileInfo, err error) : 파일의 정보를 얻어옴
> func (f *File) Read(b []byte) (n int, err error) : 파일에서 값을 읽음. 파일에서 읽은 데이의 길이와 에러 값을 리턴
- 파일 읽기
3. 파일 읽기 쓰기
- os 패키지에서 제공하는 파일 열기 함수와 파일 포인터 설정 함수
> func OpenFile(name string, flag int, perm FileMode) (file *File, err error) : 파일 플래그, 파일 모드를 지정해 파일 열기
> func (f *File) Seek(offset int64, whence int) (ret int64, err error) : 파일을 읽거나 쓸 위치로 이동
- os.OpenFile 함수를 사용해 읽기/쓰기 모드로 파일을 연 뒤 파일 읽기
- os.OpenFile 함수는 파일 이름, 플래그, 파일 모드를 받는다
- |(OR 연산자)를 사용해 조합할 수 있다
- 플래그 종류
> os.O_RDONLY : 읽기 전용으로 파일 열기
> os.O_WRONLY : 쓰기 전용으로 파일 열기
> os.O_RDWR : 읽기, 쓰기를 모두 할 수 있도록 파일 열기
> os.O_APPEND : 파일을 저장했을 때 끝부분에 내용을 추가한다
> os.O_CREATE : 파일이 없으면 파일을 새로 생성, 파일이 있으면 해당 파일 열기
> os.O_EXCL : 파일이 없을 때만 새로 생성 (os.O_CREATE와 사용)
> os.O_SYNC : 동기 I/O를 사용
> os.O_TRUNC : 파일이 있다면 파일을 연 뒤 내용 삭제
- fiel.Seek()에서 첫 매개변수는 offset 두 번째 매개변수는 기준점이다
- 설정할 수 있는 기준점
> os.SEEK_SET : 파일의 맨 처음을 기준으로 함
> os.SEEK_CUR : 현재 읽기/쓰기 OFFSET 값을 기준으로 함
> os.SEEK_END : 파일의 맨 끝을 기준으로 함
4. ioutil 패키지 사용하기
- ioutil 패키지를 사용하면 보다 간단하게 파일을 읽고 쓸 수 있음
- ioutil 패키지에서 제공하는 파일 함수
> func WriteFile(filename string, data []byte, per os.FileMode) error : 파일 쓰기, 에러 값 리턴
> func ReadFile(filename string) ([]byte, error) : 파일 읽기, 읽은 데이터(바이트 슬라이스)와 에러 값 리턴
- ioutil을 사용해 파일 읽기
[UNIT 50] 입출력 인터페이스 사용하기
- io.Reader와 io.Writer 인터페이스를 사용해 입출력을 할 수 있다
- io.Reader와 io.Writer의 정의
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
1. 파일 처리하기
- bufio 패키지에서 제공하는 입출력 인터페이스 함수
* bufio는 Buffered I/O를 뜻함
> func NewReader(rd io.Reader) *Reader : io.Reader 인터페이스로 io.Reader 인터페이스를 따르는 읽기 인스턴스 생성
> func NewWriter(w io.Writer) *Writer : io.Writer 인터페이스로 io.Writer 인터페이스를 따르는 쓰기 인스턴스 생성
> func (b *Writer) WriterString(s string) (int, error) : 문자열 버퍼에 저장
> func (b *Writer) Flush() error : 버퍼의 데이터를 파일에 저장
- bufiofmf 사용해 파일 읽고 쓰기
> bufio로 파일을 저장하게 되면 바로 저장하지 않고 임시 공간(버퍼)에 쌓아 두었다 Flush 매서드를 통해 파일에 저장한다
2. 문자열을 파일로 저장하기
- strings 패키지에서 제공하는 입출력 인터페이스 함수
> func NewReader(s string) *Reader : 문자열로 io.Reader 인터페이스를 따르는 읽기 인스턴스를 생성한다
- bufio 패키지에서 제공하는 입출력 인터페이스 함수
> func (b *Writer) ReadFrom(r io.Reader) (n int64, err error) : io.Reader의 데이터를 읽어 io.Writer에 저장
- 문자열을 io.Reader 인터페이스로 만들어 파일에 저장하기
- io.Copy 함수를 사용해 io.Reader의 데이터를 io.Writer로 복사할 수 있음
> func Copy(dst Writer, src Reader) (written int64, err error) : io.Reader를 io.Writer로 복사하기
3. 문자열을 화면에 출력하기
- 문자열을 io.Redaer를 그대로 화면에 출력하기
> os.Stdout도 io.Writer 인터페이스를 따르기에 io.Copy 함수로 io.Reader를 복사해주면 콘솔에 그대로 출력된다
4. 기본 입출력 함수 사용하기
- 기본 입출력 함수와 입출력 인터페이스를 사용해 출력하기
- bufio와 기본 입출력 함수를 사용해 파일에 값 저장하기
- io.Reader, io.Writer 인터페이스만 맞다면 어디든 사용할 수 있음
5. 읽기, 쓰기 인터페이스를 함께 사용하기
- io.ReadWriter 인터페이스로 읽기/쓰기 처리하기
* io.ReadWriter 인터페이스는 io.Reader, io.Writer 인터페이스를 포함하고 있다
type ReadWriter interface {
Reader
Writer
}
- bufio 패키지에서 제공하는 입출력 인터페이스 함수
> func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error) : io.Reader에서 문자열 한 중을 읽어 바이트 슬라이스로 리턴
- io.ReadWriter 인터페이스를 사용해 파일에 문자열 쓰고 읽기
> io.ReadWriter 인터페이스를 사용해 읽기 및 쓰기 동작을 인스턴스 한 개로 처리할 수 있음
[UNIT 51] JSON 문서 사용하기
- encoding/json 패키지에서 제공하는 JSON 함수
> func Marshal(v interface{}) ([]byte, error) : Go 언어 자료형을 JSON 텍스트로 변환
> func Marchallndent(v interface{}, prefix, indent string) ([]byte, error) : Go 언어 자료형을 JSON 텍스트로 변환하고 사람이 보기 편하도록 들여쓰기를 해준다
> func Unmarchal(data []byte, v interface{}) error : JSON 텍스트를 Go 언어 자료형으로 변환
- JSON 문서를 읽는 방법
> var data map[string ]interface{}를 통해 JSON 문서의 데이터를 저장할 공간을 간단하게 맵으로 만들 수 있다
> JSON 문서에서 키는 문자열이고 값은 문자열과 숫자를 사용한다
> 맵을 만들 때 키는 문자열로 값은 interface{}로 지정해 모든 값을 넣을 수 있게 한다
> data[“키 이름”]으로 키를 지정해 값을 가져올 수 있다
- 맵을 JSON 형태로 변환
> 문자열을 키로하고 모든 자료형을 값으로 저장할 수 있는 맵을 할당하고 json.Marshal 함수로 JSON 문서로 변환한다
> 첫 번째 매개변수는 JSON 문서로 만들 데이터
> 두 번째 매개변수는 JSON 문서의 첫 칸에 표시할 문자열
> 세 번째 매개변수는 들여쓰기 할 문자
1. 구조체 활용하기
- 구조체를 사용해 좀 더 복잡한 형태인 JSON 문서를 처리하기 (배열, 키-배열, 키-값 조합)
- JOSN 문서의 구조
- 데이터 JSON 형태로 변환하기
- 구조체 필드가 대문자로 시작하면 JSON 문서 안의 키도 대문자로 시작해야 한다
- JSON 문서 안의 키를 소문자로 시작하길 원한다면 '필드명 자료형 ‘json:”키”’로 지정해주어야 한다
2. JSON 파일 사용하기
- JSON 파일을 만들고 읽기
> JSON 파일 만들기
> 파일 읽기
'Study > Go 언어' 카테고리의 다른 글
[Go언어] 암호화, 프로토콜 (0) | 2020.08.17 |
---|---|
[Go언어] 함수, 구조체, 채널 (0) | 2020.08.01 |
[Go언어] 반복/조건문, 배열 (0) | 2020.07.25 |
[Go언어] 기본 문법 (자료형, 연산자 등) (0) | 2020.07.18 |
댓글