[고루틴 사용]
//ch24/ex24.1/ex24.1.go
package main
import (
"fmt"
"time"
)
func PrintHangul() {
hanguls := []rune{'가', '나', '다', '라', '마', '바', '사'}
for _, v := range hanguls {
time.Sleep(300 * time.Millisecond)
fmt.Printf("%c ", v)
}
}
func PrintNumbers() {
for i := 1; i <= 5; i++ {
time.Sleep(400 * time.Millisecond)
fmt.Printf("%d ", i)
}
}
func main() {
go PrintHangul()
go PrintNumbers()
time.Sleep(3 * time.Second) // ❷ 3초간 대기
}
가 1 나 2 다 라 3 마 4 바 5 사
서브 고루틴이 종료될 때까지 기다리기
//ch24/ex24.2/ex24.2.go
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup // ❶ waitGroup 객체
func SumAtoB(a, b int) {
sum := 0
for i := a; i <= b; i++ {
sum += i
}
fmt.Printf("%d부터 %d까지 합계는 %d입니다.\n", a, b, sum)
wg.Done() // ❸ 작업이 완료됨을 표시
}
func main() {
wg.Add(10) // ❷ 총 작업 개수 설정
for i := 0; i < 10; i++ {
go SumAtoB(1, 1000000000)
}
wg.Wait() // ❹ 모든 작업이 완료되길 기다림.
fmt.Println("모든 계산이 완료되었습니다.")
}
1부터 1000000000까지 합계는 500000000500000000입니다.
1부터 1000000000까지 합계는 500000000500000000입니다.
1부터 1000000000까지 합계는 500000000500000000입니다.
1부터 1000000000까지 합계는 500000000500000000입니다.
1부터 1000000000까지 합계는 500000000500000000입니다.
1부터 1000000000까지 합계는 500000000500000000입니다.
1부터 1000000000까지 합계는 500000000500000000입니다.
1부터 1000000000까지 합계는 500000000500000000입니다.
1부터 1000000000까지 합계는 500000000500000000입니다.
1부터 1000000000까지 합계는 500000000500000000입니다.
모든 계산이 완료됐습니다.
동시성 프로그래밍 주의점
//ch24/ex24.3/ex24.3.go
package main
import (
"fmt"
"sync"
"time"
)
type Account struct {
Balance int
}
func main() {
var wg sync.WaitGroup
account := &Account{0} // ❶ 0원 잔고 통장
wg.Add(10) // ❷ WaitGroup 객체 생성
for i := 0; i < 10; i++ { // ❸ Go 루틴 10개 생성
go func() {
for {
DepositAndWithdraw(account)
}
wg.Done()
}()
}
wg.Wait()
}
func DepositAndWithdraw(account *Account) {
if account.Balance < 0 { // ➍ 잔고가 0이하면 패닉
panic(fmt.Sprintf("Balance should not be negative value: %d", account.Balance))
}
account.Balance += 1000 // ➎ 1000원 입금
time.Sleep(time.Millisecond) // ➏ 잠시 쉬고
account.Balance -= 1000 // ➐ 1000원 출금
}
뮤텍스 이용한 동시성 문제 해결
//ch24/ex24.4/ex24.4.go
package main
import (
"fmt"
"sync"
"time"
)
var mutex sync.Mutex // ❶ 패키지 전역 변수 뮤텍스
type Account struct {
Balance int
}
func DepositAndWithdraw(account *Account) {
mutex.Lock() // ❷ 뮤텍스 획득
defer mutex.Unlock() // ❸ defer를 사용한 Unlock()
if account.Balance < 0 {
panic(fmt.Sprintf("Balance should not be negative value: %d", account.Balance))
}
account.Balance += 1000
time.Sleep(time.Millisecond)
account.Balance -= 1000
}
func main() {
var wg sync.WaitGroup
account := &Account{0}
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
for {
DepositAndWithdraw(account)
}
wg.Done()
}()
}
wg.Wait()
}
데드락 발생
//ch24/ex24.5/ex24.5.go
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
var wg sync.WaitGroup
func main() {
rand.Seed(time.Now().UnixNano())
wg.Add(2)
fork := &sync.Mutex{} // ❶ 포크와 수저 뮤텍스
spoon := &sync.Mutex{}
go diningProblem("A", fork, spoon, "포크", "수저") // ❷ A는 포크 먼저
go diningProblem("B", spoon, fork, "수저", "포크") // ❸ B는 수저 먼저
wg.Wait()
}
func diningProblem(name string, first, second *sync.Mutex, firstName, secondName string) {
for i := 0; i < 100; i++ {
fmt.Printf("%s 밥을 먹으려 합니다.\n", name)
first.Lock() // ❹ 첫 번째 뮤텍스를 획득 시도
fmt.Printf("%s %s 획득\n", name, firstName)
second.Lock() // ➎ 두 번째 뮤텍스를 획득 시도
fmt.Printf("%s %s 획득\n", name, secondName)
fmt.Printf("%s 밥을 먹습니다\n", name)
time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
second.Unlock() // ➏ 뮤텍스 반납
first.Unlock()
}
wg.Done()
}
B 밥을 먹으려 합니다
B 수저 획득
B 포크 획득
B 밥을 먹으려 합니다
B 수저 획득
B 포크 획득
A 밥을 먹으려 합니다
B 밥을 먹으려 합니다.
A 포크 획득
B 수저 획득
fatal error: all goroutines are asleep - deadleck!
영역을 나누는 방법
//ch24/ex24.6/ex24.6.go
package main
import (
"fmt"
"sync"
"time"
)
type Job interface { // ❶ Job 인터페이스
Do()
}
type SquareJob struct {
index int
}
func (j *SquareJob) Do() {
fmt.Printf("%d 작업 시작\n", j.index) // ❷ 각 작업
time.Sleep(1 * time.Second)
fmt.Printf("%d 작업 완료 - 결과: %d\n", j.index, j.index*j.index)
}
func main() {
var jobList [10]Job
for i := 0; i < 10; i++ { // ❸ 10가지 작업 할당
jobList[i] = &SquareJob{i}
}
var wg sync.WaitGroup
wg.Add(10)
for i := 0; i < 10; i++ {
job := jobList[i] // ❹ 각 작업을 Go 루틴으로 실행
go func() {
job.Do()
wg.Done()
}()
}
wg.Wait()
}
역할을 나누는 방법
[채널과 컨텍스트]
채널 사용하기
//ch25/ex25.1/ex25.1.go
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
ch := make(chan int) // ❶ 채널 생성
wg.Add(1)
go square(&wg, ch) // ❷ Go 루틴 생성
ch <- 9 // ❸ 채널에 데이터를 넣는다.
wg.Wait() // ❹ 작업이 완료되길 기다린다.
}
func square(wg *sync.WaitGroup, ch chan int) {
n := <-ch // ➎ 데이터를 빼온다
time.Sleep(time.Second) // 1초 대기
fmt.Printf("Square: %d\n", n*n)
wg.Done()
}
채널 닫기
//ch25/ex25.4/ex25.4.go
package main
import (
"fmt"
"sync"
"time"
)
func square(wg *sync.WaitGroup, ch chan int) {
for n := range ch { // ❷ 채널이 닫히면 종료
fmt.Printf("Square: %d\n", n*n)
time.Sleep(time.Second)
}
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()
}
select문
//ch25/ex25.5/ex25.5.go
package main
import (
"fmt"
"sync"
"time"
)
func square(wg *sync.WaitGroup, ch chan int, quit chan bool) {
for {
select { // ❷ ch와 quit 양쪽을 모두 기다린다.
case n := <-ch:
fmt.Printf("Square: %d\n", n*n)
time.Sleep(time.Second)
case <-quit:
wg.Done()
return
}
}
}
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()
}
Square: 0
Square: 4
Square: 16
Square: 36
Square: 64
Square: 100
Square: 144
Square: 196
Square: 254
Square: 324
일정 간격으로 실행
//ch25/ex25.6/ex25.6.go
package main
import (
"fmt"
"sync"
"time"
)
func square(wg *sync.WaitGroup, ch chan int) {
tick := time.Tick(time.Second) // ❶ 1초 간격 시그널
terminate := time.After(10 * time.Second) // ❷ 10초 이후 시그널
for {
select { // ❸ tick, terminate, ch 순서로 처리
case <-tick:
fmt.Println("Tick")
case <-terminate:
fmt.Println("Terminated!")
wg.Done()
return
case n := <-ch:
fmt.Printf("Square: %d\n", n*n)
time.Sleep(time.Second)
}
}
}
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
}
wg.Wait()
}
Square: 0
Square: 4
Square: 16
Tick
Square: 36
Square: 64
Tick
Tick
채널로 생산자 소비자 패턴 구현하기
//ch25/ex25.7/ex25.7.go
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) // ❶ Go 루틴 생성
go InstallTire(tireCh, paintCh)
go PaintCar(paintCh)
wg.Wait()
fmt.Println("Close the factory")
}
func MakeBody(tireCh chan *Car) { // ❷ 차체 생산
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: // ❸ 10초 뒤 종료
close(tireCh)
wg.Done()
return
}
}
}
func InstallTire(tireCh, paintCh chan *Car) { // ❹ 바퀴 설치
for car := range tireCh {
// Make a body
time.Sleep(time.Second)
car.Tire = "Winter tire"
paintCh <- car
}
wg.Done()
close(paintCh)
}
func PaintCar(paintCh chan *Car) { // ➎ 도색
for car := range paintCh {
// Make a body
time.Sleep(time.Second)
car.Color = "Red"
duration := time.Now().Sub(startTime) // ➏ 경과 시간 출력
fmt.Printf("%.2f Complete Car: %s %s %s\n", duration.Seconds(), car.Body, car.Tire, car.Color)
}
wg.Done()
}
Start Factory
3.00 Complete Car: Sports car Winter tire Red
4.00 Complete Car: Sports car Winter tire Red
5.00 Complete Car: Sports car Winter tire Red
6.00 Complete Car: Sports car Winter tire Red
7.00 Complete Car: Sports car Winter tire Red
8.00 Complete Car: Sports car Winter tire Red
9.00 Complete Car: Sports car Winter tire Red
10.00 Complete Car: Sports car Winter tire Red
11.00 Complete Car: Sports car Winter tire Red
12.00 Complete Car: Sports car Winter tire Red
Close the factory
작업 취소가 가능한 컨텍스트
//ch25/ex25.8/ex25.8.go
package main
import (
"context"
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func main() {
wg.Add(1)
ctx, cancel := context.WithCancel(context.Background()) // ❶ 컨텍스트 생성
// ctx, cancel := context.WithTimeout(context.Backgrout(), 3*time.Second) // 일정 시간만 작업하는 컨텍스트 3초뒤에 종료되는 컨텍스트
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")
}
}
}
특정 값을 설정한 컨텍스트
//ch25/ex25.9/ex25.9.go
package main
import (
"context"
"fmt"
"sync"
)
var wg sync.WaitGroup
func main() {
wg.Add(1)
ctx := context.WithValue(context.Background(), "number", 9) // ❶ 컨텍스트에 값을 추가한다
go square(ctx)
wg.Wait()
}
func square(ctx context.Context) {
if v := ctx.Value("number"); v != nil { // ❷ 컨텍스트에서 값을 읽어온다.
n := v.(int)
fmt.Printf("Square:%d", n*n)
}
wg.Done()
}
Square:81
pub sub 패턴
pub
package main
import "context"
type Publisher struct {
ctx context.Context
subscribeCh chan chan<- string
publishCh chan string
subscribers []chan<- string
}
func NewPublisher(ctx context.Context) *Publisher {
return &Publisher{
ctx: ctx,
subscribeCh: make(chan chan<- string),
publishCh: make(chan string),
subscribers: make([]chan<- string, 0),
}
}
func (p *Publisher) Subscribe(sub chan<- string) {
p.subscribeCh <- sub
}
func (p *Publisher) Publish(msg string) {
p.publishCh <- msg
}
func (p *Publisher) Update() {
for {
select {
case sub := <-p.subscribeCh:
p.subscribers = append(p.subscribers, sub)
case msg := <-p.publishCh:
for _, subscriber := range p.subscribers {
subscriber <- msg
}
case <-p.ctx.Done():
wg.Done()
return
}
}
}
sub
package main
import (
"context"
"fmt"
)
type Subscriber struct {
ctx context.Context
name string
msgCh chan string
}
func NewSubscriber(name string, ctx context.Context) *Subscriber {
return &Subscriber{
ctx: ctx,
name: name,
msgCh: make(chan string),
}
}
func (s *Subscriber) Subscribe(pub *Publisher) {
pub.Subscribe(s.msgCh)
}
func (s *Subscriber) Update() {
for {
select {
case msg := <-s.msgCh:
fmt.Printf("%s got Message:%s\n", s.name, msg)
case <-s.ctx.Done():
wg.Done()
return
}
}
}
main
package main
import (
"context"
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func main() {
ctx, cancel := context.WithCancel(context.Background())
wg.Add(4)
publisher := NewPublisher(ctx)
subscriber1 := NewSubscriber("AAA", ctx)
subscriber2 := NewSubscriber("BBB", ctx)
go publisher.Update()
subscriber1.Subscribe(publisher)
subscriber2.Subscribe(publisher)
go subscriber1.Update()
go subscriber2.Update()
go func() {
tick := time.Tick(time.Second * 2)
for {
select {
case <-tick:
publisher.Publish("Hello Message")
case <-ctx.Done():
wg.Done()
return
}
}
}()
fmt.Scanln()
cancel()
wg.Wait()
}
[단어 검색 프로그램 만들기]
//ch26/ex26.5/ex26.5.go
package main
import (
"bufio"
"fmt"
"os"
"path/filepath"
"strings"
)
type LineInfo struct {
lineNo int
line string
}
type FindInfo struct {
filename string
lines []LineInfo
}
이 코드는 주어진 파일에서 찾은 줄 정보를 저장하기 위한 LineInfo 구조체와 해당 파일의 이름과 줄 정보를 저장하기 위한 FindInfo 구조체를 정의하고 있습니다.
- LineInfo 구조체: 줄 번호(lineNo)와 줄 내용(line)을 저장하기 위한 구조체입니다.
- FindInfo 구조체: 파일 이름(filename)과 해당 파일에서 찾은 줄 정보(lines)를 저장하기 위한 구조체입니다. lines는 LineInfo 구조체의 슬라이스입니다.
이러한 구조체를 사용하여 파일 검색 프로그램에서 파일명과 해당 파일에서 찾은 줄 정보를 구조화하여 저장할 수 있습니다.
func main() {
if len(os.Args) < 3 { //
fmt.Println("2개 이상의 실행인자가 필요합니다. ex) ex26.5 word filepath")
return
}
word := os.Args[1]
path := os.Args[2]
cnt, ch := FindWordInAllFiles(word, path)
recvCnt := 0
for findInfo := range ch {
fmt.Println(findInfo.filename)
fmt.Println("--------------------------------")
for _, lineInfo := range findInfo.lines {
fmt.Println("\t", lineInfo.lineNo, "\t", lineInfo.line)
}
fmt.Println("--------------------------------")
fmt.Println()
recvCnt++
if recvCnt == cnt {
// all received
break
}
}
}
이 코드는 주어진 단어를 여러 파일에서 찾아 해당 파일과 해당 단어가 포함된 줄을 출력하는 기능을 수행합니다.
- if len(os.Args) < 3 { ... }: os.Args는 프로그램 실행 시 전달된 명령행 인수들을 담은 슬라이스입니다. 이 코드는 전달된 명령행 인수의 개수가 3개 미만인 경우에 오류 메시지를 출력하고 프로그램을 종료합니다. 예를 들어, ex26.5 word filepath와 같이 최소 2개 이상의 인수가 필요합니다.
- word := os.Args[1]: os.Args의 두 번째 요소를 word 변수에 할당합니다. 이는 찾을 단어를 나타냅니다.
- path := os.Args[2]: os.Args의 세 번째 요소를 path 변수에 할당합니다. 이는 파일을 검색할 경로를 나타냅니다.
- cnt, ch := FindWordInAllFiles(word, path): FindWordInAllFiles() 함수를 호출하여 주어진 단어를 모든 파일에서 찾고, 찾은 결과를 반환합니다. cnt는 찾은 파일의 개수를 나타내는 변수이고, ch는 결과를 전달받을 채널입니다.
- recvCnt := 0: 결과를 받은 횟수를 나타내는 변수를 초기화합니다.
- for findInfo := range ch { ... }: 채널 ch로부터 결과를 받아오는 반복문입니다. findInfo는 찾은 결과를 나타내는 구조체입니다. 반복문은 채널로부터 값을 받을 때까지 계속해서 실행됩니다.
- fmt.Println(findInfo.filename): 찾은 파일의 이름을 출력합니다.
- fmt.Println("--------------------------------"): 구분선을 출력합니다
- for _, lineInfo := range findInfo.lines { ... }: 찾은 파일에서 해당 단어가 포함된 줄 정보를 반복하여 출력합니다. lineInfo는 줄 정보를 나타내는 구조체입니다.
- fmt.Println("\t", lineInfo.lineNo, "\t", lineInfo.line): 줄 번호와 줄 내용을 탭으로 구분하여 출력합니다.
- fmt.Println(): 줄바꿈을 통해 결과 사이에 공백을 추가합니다.
- recvCnt++: 결과를 받은 횟수를 증가시킵니다.
- if recvCnt == cnt { ... }: 받은 결과의 개수가 찾은 파일의 개수와 같아진 경우에는 모든 결과를 받았으므로 반복문을 종료합니다.
이 코드는 주어진 단어를 파일에서 찾아 해당 파일과 해당 단어가 포함된 줄을 출력하는 간단한 프로그램입니다.
func GetFileList(pattern string) ([]string, error) {
filelist := []string{}
err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
matched, _ := filepath.Match(pattern, info.Name())
if matched {
filelist = append(filelist, path)
}
}
return nil
})
if err != nil {
return []string{}, err
}
return filelist, nil
}
이 코드는 주어진 패턴과 일치하는 파일을 현재 디렉토리를 포함한 하위 디렉토리에서 검색하여 파일 목록을 반환하는 GetFileList 함수를 정의하고 있습니다.
- filelist := []string{}: 파일 목록을 저장할 빈 문자열 슬라이스인 filelist을 생성합니다.
- err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { ... }): filepath.Walk 함수를 호출하여 현재 디렉토리부터 하위 디렉토리까지 파일을 재귀적으로 탐색합니다. 탐색 과정에서 각 파일에 대해 지정된 함수를 호출합니다. 이 함수는 path에 파일 경로, info에 파일 정보, err에 발생한 오류 정보가 전달됩니다.
- if !info.IsDir() { ... }: 파일 정보 info가 디렉토리가 아닌 경우에만 조건문 내의 코드를 실행합니다. 디렉토리는 파일 목록에 포함시키지 않습니다.
- matched, _ := filepath.Match(pattern, info.Name()): filepath.Match 함수를 사용하여 파일 이름과 주어진 패턴을 비교하여 일치하는지 여부를 확인합니다. matched 변수에 일치 여부가 할당됩니다. 두 번째 반환 값인 _는 오류 정보를 무시합니다.
- if matched { ... }: 파일 이름과 패턴이 일치하는 경우에만 조건문 내의 코드를 실행합니다.
- filelist = append(filelist, path): filelist 슬라이스에 해당 파일의 경로를 추가합니다.
- return nil: 현재 파일 탐색에서 오류가 발생하지 않았으므로 nil을 반환하여 계속 탐색을 진행합니다.
- if err != nil { ... }: filepath.Walk 함수 호출 도중 오류가 발생한 경우, 오류를 반환합니다.
- return filelist, nil: 파일 탐색이 완료되었고, 오류가 없는 경우에는 filelist을 반환합니다. 파일 목록이 비어있을 수도 있으므로 빈 슬라이스도 반환할 수 있습니다.
이렇게 GetFileList 함수는 현재 디렉토리부터 하위 디렉토리까지 파일을 재귀적으로 탐색하여 주어진 패턴과 일치하는 파일들의 목록을 반환합니다. 이 함수는 filepath.Walk 함수를 사용하여 파일 시스템을 순회하고, 일치하는 파일을 filelist에 추가하는 방식으로 동작합니다.
func FindWordInAllFiles(word, path string) (int, chan FindInfo) {
filelist, err := GetFileList(path) // 실행인자 가져오기
if err != nil {
fmt.Println("파일을 찾을 수 없습니다. err:", err)
return 0, nil
}
ch := make(chan FindInfo)
cnt := len(filelist)
for _, filename := range filelist {
go FindWordInFile(word, filename, ch)
}
return cnt, ch
}
이 코드는 주어진 경로에서 모든 파일을 찾아서 각 파일마다 FindWordInFile 함수를 고루틴으로 실행하고, 각 파일의 결과를 수신할 수 있는 채널을 반환하는 함수인 FindWordInAllFiles를 정의하고 있습니다.
- filelist, err := GetFileList(path): GetFileList 함수를 호출하여 주어진 경로에서 파일 목록을 가져옵니다. GetFileList 함수는 경로를 기반으로 파일 목록을 검색하고 슬라이스로 반환합니다. 반환된 파일 목록과 오류 정보는 filelist와 err 변수에 할당됩니다.
- if err != nil { ... }: 파일 목록을 가져오는 도중 오류가 발생한 경우, 오류 메시지를 출력하고 nil 채널과 함께 함수를 종료합니다. 이를 통해 파일을 찾을 수 없는 경우에도 결과를 전송할 수 없으므로 nil 채널을 반환합니다.
- ch := make(chan FindInfo): FindInfo 구조체를 전달받을 수 있는 채널 ch를 생성합니다.
- cnt := len(filelist): 파일 목록의 개수를 cnt 변수에 저장합니다. 이는 전체 파일 개수를 나타냅니다.
- for _, filename := range filelist { ... }: 파일 목록에서 각 파일 이름을 가져와서 반복합니다
- go FindWordInFile(word, filename, ch): FindWordInFile 함수를 고루틴으로 실행합니다. word는 찾을 단어, filename은 파일 이름, ch는 결과를 전달받을 채널입니다. 각 파일마다 고루틴을 실행하여 동시에 여러 파일을 검색할 수 있습니다.
- return cnt, ch: 파일 개수와 결과를 전달받을 수 있는 채널 ch를 반환합니다. 이를 통해 메인 함수에서 파일 검색 결과를 수신할 수 있게 됩니다.
이렇게 FindWordInAllFiles 함수는 주어진 경로에서 모든 파일을 찾고, 각 파일에 대해 FindWordInFile 함수를 고루틴으로 실행하여 파일별 결과를 수신할 수 있는 채널을 반환합니다. 이를 통해 여러 파일을 동시에 검색할 수 있습니다.
func FindWordInFile(word, filename string, ch chan FindInfo) {
findInfo := FindInfo{filename, []LineInfo{}}
file, err := os.Open(filename)
if err != nil {
fmt.Println("파일을 찾을 수 없습니다. ", filename)
ch <- findInfo
return
}
defer file.Close()
lineNo := 1
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, word) {
findInfo.lines = append(findInfo.lines, LineInfo{lineNo, line})
}
lineNo++
}
ch <- findInfo
}
이 코드는 주어진 단어를 특정 파일에서 찾아 해당 파일과 해당 단어가 포함된 줄 정보를 FindInfo 구조체로 묶어 채널로 전송하는 함수인 FindWordInFile을 정의하고 있습니다.
- findInfo := FindInfo{filename, []LineInfo{}}: filename과 빈 줄 정보 슬라이스를 가지고 있는 FindInfo 구조체를 생성합니다. FindInfo는 파일 이름과 해당 파일에서 찾은 줄 정보를 저장하기 위한 구조체입니다.
- file, err := os.Open(filename): 주어진 filename을 사용하여 파일을 엽니다. 열기 도중 오류가 발생한 경우 err에 에러 정보가 저장됩니다.
- if err != nil { ... }: 파일 열기 도중 오류가 발생한 경우, 오류 메시지를 출력하고 findInfo를 채널 ch로 전송한 뒤 함수를 종료합니다. 이를 통해 파일을 찾을 수 없는 경우에도 결과를 전송하여 프로그램의 흐름을 유지할 수 있습니다.
- defer file.Close(): 파일을 열었으면 함수 종료 전에 파일을 닫습니다. defer 키워드를 사용하여 함수가 종료되기 직전에 file.Close()가 실행되도록 합니다. 이를 통해 파일 리소스 누수를 방지할 수 있습니다.
- lineNo := 1: 줄 번호를 초기화합니다. 이 변수는 각 줄의 번호를 나타냅니다.
- scanner := bufio.NewScanner(file): 파일을 한 줄씩 읽기 위해 bufio.Scanner를 사용하여 스캐너 객체를 생성합니다.
- for scanner.Scan() { ... }: 스캐너를 통해 파일을 한 줄씩 읽으면서 반복합니다.
- line := scanner.Text(): 스캐너의 Text() 메서드를 호출하여 현재 읽은 줄의 내용을 가져옵니다.
- if strings.Contains(line, word) { ... }: strings.Contains() 함수를 사용하여 line 문자열에 word가 포함되어 있는지 확인합니다. 만약 word가 포함되어 있다면, 해당 줄의 정보를 findInfo.lines에 추가합니다. LineInfo 구조체는 줄 번호와 줄 내용을 저장합니다.
- lineNo++: 줄 번호를 증가시킵니다.
- ch <- findInfo: findInfo 구조체를 채널 ch로 전송합니다. 이를 통해 파일 검색 결과를 메인 함수에서 받을 수 있게 됩니다.
이렇게 FindWordInFile 함수는 주어진 단어를 특정 파일에서 찾아 해당 파일과 해당 단어가 포함된 줄 정보를 FindInfo 구조체로 묶어 채널을 통해 전송합니다. 이 함수는 파일을 열고 찾은 결과를 처리하는 간단한 파일 검색 로직을 포함합니다.
[참조]
https://www.youtube.com/@TuckerProgramming
Tucker Programming
Tucker 의 프로그래밍 강좌 채널입니다. 질문은 댓글로 남겨주시면 답변 드리겠습니다.
www.youtube.com
댓글