Go: Goの同期プリミティブ:Mutex/RWMutex、sync.Once、sync.Map、atomic、レース検出…

チャネルはゴルーチン間の通信に使用され、sync パッケージは共有状態を保護するために使用されます。Go は並行プログラミングのための包括的なツールキットを提供しています。

複数のゴルーチンが同じ変数から読み込んだり、その変数に書き込んだりする必要がある場合、安全性を確保するために同期プリミティブが必要となります。このレッスンでは、Goのsyncパッケージの主要なツールと選択戦略についてすべて学びます。

1. 学習内容



2. 高同時実行環境を担当するエンジニアの実話

(1) 問題:100個のgoroutineが同時にカウンタに書き込みを行うため、データが完全に破損してしまう

チャーリーは、あるECプラットフォームのバックエンドエンジニアで、1秒あたりの注文数をカウントする必要があります:

「100個のgoroutineが同時に注文を処理しており、各goroutineは注文を完了するたびにカウンターを1ずつ増やしていました。サービス開始後の初日、データに大きな乖離が生じました。ダッシュボードには1秒あたり1,000件の注文が表示されていたのに、データベースには300件しか記録されていなかったのです。上司から『残りの700件分の注文代金はどこへ消えたんだ?』と問いただされました。」

彼はコードを開いた:

GO
// Bad code: concurrent write to shared variable, no lock
var counter int

func processOrder(orderID string) {
    // ... process order
    counter++  // Non-atomic operation! Equivalent to:
               // temp = counter
               // temp = temp + 1
               // counter = temp  <- 3 goroutines executing this step simultaneously = chaos
}

counter++ はアトミックな操作ではありません。これは、読み取り、1 増分、書き込みという 3 つのステップに分解されます。もし 2 つのゴルーチンが同時に counter=10 を読み取り、1 増分し、それを書き戻した場合、結果は 12 ではなく counter=11 になってしまいます。これは データ競合 の典型的な例です。

(2) Go言語での解決策:ミューテックス

GO
// counter.go
package main

import (
    "fmt"
    "sync"
)

type SafeCounter struct {
    mu    sync.Mutex
    value int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()
    c.value++ // Protected by the lock; only one goroutine can execute at a time
    c.mu.Unlock()
}

func (c *SafeCounter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.value
}

func main() {
    var counter SafeCounter
    var wg sync.WaitGroup

    // 100 goroutines incrementing simultaneously
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter.Increment()
        }()
    }

    wg.Wait()
    fmt.Printf("Final value: %d (expected 100)\n", counter.Value())
}

出力:

TEXT 📖 参照専用
Final value: 100 (expected 100)

(3) リターン:ロック状態とロック解除状態

状況 結果 データ競合
ロックフリーの並行処理 95/100/97 (ランダム) ✅ はい (レースコンディションが検出されました)
ミューテックス保護 100 ❌ なし
原子操作 100 ❌ なし
💡 ヒント: go run -race main.go はデータ競合を検出できます。共有変数への並行アクセスがロックによって保護されていない場合、-race オプションは実行時に警告を出力します。CI/CD では、常に -race を有効にすることをお勧めします。



3. sync.Mutex ミューテックス

(1) ミューテックスの基礎

GO
package main

import (
    "fmt"
    "sync"
)

type Account struct {
    mu      sync.Mutex
    balance float64
}

func (a *Account) Deposit(amount float64) {
    a.mu.Lock()
    a.balance += amount
    a.mu.Unlock()
}

func (a *Account) Withdraw(amount float64) error {
    a.mu.Lock()
    defer a.mu.Unlock()
    if a.balance < amount {
        return fmt.Errorf("insufficient balance: have %.2f, need %.2f", a.balance, amount)
    }
    a.balance -= amount
    return nil
}

func (a *Account) Balance() float64 {
    a.mu.Lock()
    defer a.mu.Unlock()
    return a.balance
}

func main() {
    acc := Account{balance: 1000}
    var wg sync.WaitGroup

    // 10 concurrent deposits
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            acc.Deposit(100)
        }()
    }
    wg.Wait()

    fmt.Printf("Balance: %.2f\n", acc.Balance())
}

▶ サンプル:ロック解除パターンの延期(推奨)

GO
// Recommended: Lock then immediately defer Unlock
func (a *Account) SafeMethod() {
    a.mu.Lock()
    defer a.mu.Unlock()
    // All operations are within the lock
    a.balance += 100
    a.balance -= 50
    // Even if there is a panic in between, defer ensures Unlock
}

// Not recommended: manual Unlock
func (a *Account) UnsafeMethod() {
    a.mu.Lock()
    a.balance += 100
    a.mu.Unlock() // If there is a return or panic in between, Unlock won't be called → deadlock
}
▶ 試してみよう
🔥 よくある間違い: Lock の後には、必ず Unlock を呼び出す必要があります。 最後に手動で Unlock を呼び出すのではなく、常に defer a.mu.Unlock() を使用してください。複数の戻りパスがある場合、そのいずれかで Unlock の呼び出しを忘れると、デッドロックが発生します。



4. sync.RWMutex 読み書きロック

(1) 読み書きロックの原理

GO
package main

import (
    "fmt"
    "sync"
    "time"
)

type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}

func NewCache() *Cache {
    return &Cache{data: make(map[string]string)}
}

// Read operation: RLock (concurrent reads, non-exclusive)
func (c *Cache) Get(key string) (string, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    v, ok := c.data[key]
    return v, ok
}

// Write operation: Lock (exclusive, blocks all reads and writes)
func (c *Cache) Set(key, value string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.data[key] = value
}

func main() {
    cache := NewCache()
    var wg sync.WaitGroup

    // Multiple concurrent reads (RLock is non-exclusive)
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            for j := 0; j < 3; j++ {
                if v, ok := cache.Get("key"); ok {
                    fmt.Printf("Reader %d: %s\n", id, v)
                }
                time.Sleep(10 * time.Millisecond)
            }
        }(i)
    }

    // One writer
    wg.Add(1)
    go func() {
        defer wg.Done()
        time.Sleep(5 * time.Millisecond)
        cache.Set("key", "value")
        fmt.Println("Writer: write complete")
    }()

    wg.Wait()
}

▶ サンプル:ミューテックスとRWMutexのパフォーマンス比較

GO
package main

import (
    "sync"
    "testing"
)

type DataStore struct {
    mu    sync.RWMutex
    value int
}

func BenchmarkMutexRead(b *testing.B) {
    store := DataStore{}
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            store.mu.Lock()
            _ = store.value
            store.mu.Unlock()
        }
    })
}

func BenchmarkRWMutexRead(b *testing.B) {
    store := DataStore{}
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            store.mu.RLock()
            _ = store.value
            store.mu.RUnlock()
        }
    })
}

// go test -bench=. -benchmem
▶ 試してみよう

(3) ミューテックスとRWMutexの比較

機能 ミューテックス RWMutex
読み取りと読み取り 排他 非排他
読み書き 排他制御 排他制御
書き込みと書き込み 排他 排他
ユースケース 読み書き比率 1:1 読み取りが書き込みを大幅に上回る(例:キャッシュ)
読み取り性能 低速(シリアル化) 高速(並列読み取り)


5. sync.Once:1回だけ実行する

(1) 基本を一度

GO
package main

import (
    "fmt"
    "sync"
)

var (
    config     map[string]string
    configOnce sync.Once
)

func loadConfig() {
    configOnce.Do(func() {
        fmt.Println("Initializing config...")
        config = map[string]string{
            "host": "localhost",
            "port": "8080",
        }
    })
}

func main() {
    var wg sync.WaitGroup
    // 100 goroutines call simultaneously, but init executes only once
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            loadConfig()
        }()
    }
    wg.Wait()
    fmt.Printf("config: %v\n", config)
}

出力:

TEXT 📖 参照専用
Initializing config...
config: map[host:localhost port:8080]

(2) sync.Once 対 手動フラグ

メソッド スレッドセーフ コードサイズ
if !initialized { ... } ❌ データ競合 軽微
sync.Once ✅ Goの保証 最小限
init() 関数 ✅ パッケージの読み込み時に実行される 最小限
💡 ヒント: sync.Once を使用すると、たとえ 1,000 の goroutine から同時に呼び出されたとしても、Do 内の関数が 1 回だけ実行されることが保証されます。その内部実装ではアトミック操作とミューテックスが使用されており、非常に効率的です。



6. sync.Map:並行処理に安全なマップ

(1) sync.Map の基本

GO
package main

import (
    "fmt"
    "sync"
)

func main() {
    var m sync.Map
    var wg sync.WaitGroup

    // Concurrent writes
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(n int) {
            defer wg.Done()
            m.Store(fmt.Sprintf("key-%d", n), n*100)
        }(i)
    }
    wg.Wait()

    // Read
    m.Range(func(key, value interface{}) bool {
        fmt.Printf("%s = %d\n", key, value)
        return true
    })

    // LoadOrStore: returns if exists, stores if not
    actual, loaded := m.LoadOrStore("key-0", 999)
    fmt.Printf("LoadOrStore: actual=%d, loaded=%v\n", actual, loaded)
}

▶ サンプル:sync.Map 対 map+Mutex

GO
package main

import (
    "sync"
    "testing"
)

// map + Mutex
type MutexMap struct {
    mu    sync.Mutex
    items map[string]int
}

func (m *MutexMap) Store(key string, value int) {
    m.mu.Lock()
    m.items[key] = value
    m.mu.Unlock()
}

func (m *MutexMap) Load(key string) (int, bool) {
    m.mu.Lock()
    v, ok := m.items[key]
    m.mu.Unlock()
    return v, ok
}

func BenchmarkMutexMap(b *testing.B) {
    m := &MutexMap{items: make(map[string]int)}
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            m.Store("key", 1)
            m.Load("key")
        }
    })
}

func BenchmarkSyncMap(b *testing.B) {
    var m sync.Map
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            m.Store("key", 1)
            m.Load("key")
        }
    })
}

// go test -bench=. -benchmem
▶ 試してみよう

(3) sync.Map のユースケース

シナリオ 推奨事項 理由
キーは1回のみ書き込み sync.Map 読み取り最適化(書き込みは少なく、読み取りが多い)
頻繁なキーの更新 map+Mutex sync.Map の書き込みがわずかに遅い
キーセットの拡大 sync.Map マップ全体のロックを避ける
シンプルなシナリオ マップ+ミューテックス より直感的
高性能要件 特定のベンチマーク 実際のテスト結果に基づく


7. sync/atomic:アトミック操作

(1) 原子の基礎

GO
package main

import (
    "fmt"
    "sync/atomic"
)

func main() {
    var counter int64

    // Atomic increment
    newVal := atomic.AddInt64(&counter, 1)
    fmt.Printf("AddInt64: %d\n", newVal)

    // Atomic read
    val := atomic.LoadInt64(&counter)
    fmt.Printf("LoadInt64: %d\n", val)

    // Atomic write
    atomic.StoreInt64(&counter, 100)

    // CAS (Compare And Swap)
    swapped := atomic.CompareAndSwapInt64(&counter, 100, 200)
    fmt.Printf("CAS: swapped=%v, val=%d\n", swapped, atomic.LoadInt64(&counter))
}

▶ サンプル:アトミックカウンタとミューテックス

GO 📖 参照専用
package main

import (
    "fmt"
    "sync"
    "sync/atomic"
    "time"
)

type AtomicCounter struct {
    value int64
}

func (c *AtomicCounter) Increment() {
    atomic.AddInt64(&c.value, 1)
}

func (c *AtomicCounter) Value() int64 {
    return atomic.LoadInt64(&c.value)
}

type MutexCounter struct {
    mu    sync.Mutex
    value int
}

func (c *MutexCounter) Increment() {
    c.mu.Lock()
    c.value++
    c.mu.Unlock()
}

func (c *MutexCounter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.value
}

func main() {
    var wg sync.WaitGroup
    n := 100000

    // Atomic counter
    atomicStart := time.Now()
    var ac AtomicCounter
    for i := 0; i < n; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            ac.Increment()
        }()
    }
    wg.Wait()
    fmt.Printf("Atomic: %d (%v)\n", ac.Value(), time.Since(atomicStart))

    // Mutex counter
    muStart := time.Now()
    var mc MutexCounter
    for i := 0; i < n; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            mc.Increment()
        }()
    }
    wg.Wait()
    fmt.Printf("Mutex: %d (%v)\n", mc.Value(), time.Since(muStart))
}
論理コード 56 行(40 行制限超過、参照専用)

(3) 一般的な原子関数

操作 機能 目的
加算/減算 AddInt64, AddUint32 カウンタ
読む LoadInt64, LoadPointer 安全に読む
書き込み StoreInt64, StorePointer 安全な書き込み
CAS CompareAndSwapInt64 オプティミスティックロック
スワップ SwapInt64 アトミックスワップ


8. レース検出器

▶ サンプル:データ競合の検出

GO
// race_example.go
package main

import (
    "fmt"
    "sync"
)

func main() {
    var counter int
    var wg sync.WaitGroup

    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter++ // Data race!
        }()
    }

    wg.Wait()
    fmt.Println(counter)
}
▶ 試してみよう
BASH
$ go run -race race_example.go
==================
WARNING: DATA RACE
Read at 0x00c0000... by goroutine X
  main.main.func1()
      race_example.go:14 +0x...

Previous write at 0x00c0000... by goroutine Y
  main.main.func1()
      race_example.go:14 +0x...
==================


9. sync.Pool オブジェクトプール

▶ サンプル:オブジェクトプール

GO
package main

import (
    "fmt"
    "sync"
)

type User struct {
    Name string
    Age  int
}

var userPool = sync.Pool{
    New: func() interface{} {
        return &User{}
    },
}

func main() {
    // Get from pool (avoids allocating a new object each time)
    u := userPool.Get().(*User)
    u.Name = "Alice"
    u.Age = 28
    fmt.Printf("Using: %+v\n", u)

    // Put back into the pool after use
    userPool.Put(u)

    // Next Get reuses the previous object
    u2 := userPool.Get().(*User)
    fmt.Printf("Reusing: %+v\n", u2)
}
▶ 試してみよう

10. 完全な例:高並行性のカウンタ

GO
// concurrent_counter.go
package main

import (
    "fmt"
    "sync"
    "sync/atomic"
    "time"
)

// ---------- 3 counter variants ----------

type Counter interface {
    Increment()
    Value() int64
}

// AtomicCounter
type AtomicCounter struct {
    value int64
}

func (c *AtomicCounter) Increment() {
    atomic.AddInt64(&c.value, 1)
}

func (c *AtomicCounter) Value() int64 {
    return atomic.LoadInt64(&c.value)
}

// MutexCounter
type MutexCounter struct {
    mu    sync.Mutex
    value int64
}

func (c *MutexCounter) Increment() {
    c.mu.Lock()
    c.value++
    c.mu.Unlock()
}

func (c *MutexCounter) Value() int64 {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.value
}

// ChannelCounter
type ChannelCounter struct {
    ch    chan struct{}
    value int64
    done  chan struct{}
}

func NewChannelCounter() *ChannelCounter {
    c := &ChannelCounter{
        ch:   make(chan struct{}, 100),
        done: make(chan struct{}),
    }
    go func() {
        for range c.ch {
            c.value++
        }
        close(c.done)
    }()
    return c
}

func (c *ChannelCounter) Increment() {
    c.ch <- struct{}{}
}

func (c *ChannelCounter) Value() int64 {
    return atomic.LoadInt64(&c.value)
}

func (c *ChannelCounter) Close() {
    close(c.ch)
    <-c.done
}

// ---------- Benchmark ----------

func benchmarkCounter(c Counter, goroutines, increments int) time.Duration {
    var wg sync.WaitGroup
    start := time.Now()

    for g := 0; g < goroutines; g++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for i := 0; i < increments; i++ {
                c.Increment()
            }
        }()
    }

    wg.Wait()
    return time.Since(start)
}

func main() {
    const (
        goroutines = 100
        increments = 10000
    )

    fmt.Printf("Benchmark: %d goroutines, each executing %d Increments\n\n", goroutines, increments)

    // 1. Atomic counter
    ac := &AtomicCounter{}
    atomicTime := time.Now()
    atomicDone := make(chan struct{})
    go func() {
        defer close(atomicDone)
        var wg sync.WaitGroup
        for g := 0; g < goroutines; g++ {
            wg.Add(1)
            go func() {
                defer wg.Done()
                for i := 0; i < increments; i++ {
                    ac.Increment()
                }
            }()
        }
        wg.Wait()
    }()
    <-atomicDone
    fmt.Printf("AtomicCounter: %d (expected %d) — %v\n",
        ac.Value(), goroutines*increments, time.Since(atomicTime))

    // 2. Mutex counter
    mc := &MutexCounter{}
    mutexTime := time.Now()
    mutexDone := make(chan struct{})
    go func() {
        defer close(mutexDone)
        var wg sync.WaitGroup
        for g := 0; g < goroutines; g++ {
            wg.Add(1)
            go func() {
                defer wg.Done()
                for i := 0; i < increments; i++ {
                    mc.Increment()
                }
            }()
        }
        wg.Wait()
    }()
    <-mutexDone
    fmt.Printf("MutexCounter: %d (expected %d) — %v\n",
        mc.Value(), goroutines*increments, time.Since(mutexTime))

    // 3. Channel counter
    cc := NewChannelCounter()
    channelTime := time.Now()
    channelDone := make(chan struct{})
    go func() {
        defer close(channelDone)
        var wg sync.WaitGroup
        for g := 0; g < goroutines; g++ {
            wg.Add(1)
            go func() {
                defer wg.Done()
                for i := 0; i < increments; i++ {
                    cc.Increment()
                }
            }()
        }
        wg.Wait()
        cc.Close()
    }()
    <-channelDone
    fmt.Printf("ChannelCounter: %d (expected %d) — %v\n",
        cc.Value(), goroutines*increments, time.Since(channelTime))

    fmt.Println("\nConclusion: Atomic > Mutex > Channel (performance from high to low)")
}
100%
sequenceDiagram
    participant G1 as Goroutine 1
    participant G2 as Goroutine 2
    participant G3 as Goroutine 3
    participant M as Mutex
    
    G1->>M: Lock()
    Note over G1,M: G1 holds the lock
    G2->>M: Lock() blocked
    G3->>M: Lock() blocked
    G1->>M: Unlock()
    M-->>G2: wake up
    M-->>G3: still blocked
    G2->>M: Lock() success
    Note over G2,M: G2 holds the lock
    G2->>M: Unlock()
    M-->>G3: wake up
    G3->>M: Lock() success
🔥 よくある間違い: sync.Mutex はコピーできません。Mutex を含む構造体を値渡し(ポインタ渡しではなく)で渡すと、Mutex の状態がコピーされてしまい、予期しないロック解除が発生します。必ずポインタ渡しで渡してください (*Counter)。


❓ よくある質問

Q ミューテックスとRWMutexのどちらを選べばよいですか?
A 読み取りが書き込みを大幅に上回る場合(10:1以上)→ RWMutex;読み取りと書き込みがほぼ同数、または書き込みが読み取りを上回る場合→ ミューテックス。RWMutexのRLockは並行読み取りを許可しますが、ロック自体はすべての読み取りと書き込みを相互化します。適切なものを選ばないと、パフォーマンスが低下します。
Q sync.Once はどのように使いますか?
A sync.Once を使用すると、たとえ 1,000 の goroutine が同時に呼び出したとしても、f が 1 回だけ実行されることが保証されます。代表的な用途:設定の遅延読み込み、シングルトンパターン、および一度だけ初期化が必要なリソースなどです。内部的には、アトミック操作とミューテックスを使用して実装されています。
Q sync.Map はどのような場面に適していますか?
A 公式ドキュメントでは、次の 2 つの場面が推奨されています:(1) キーが 1 回だけ書き込まれ、複数回読み込まれる場合(キャッシュ);(2) 複数の goroutine が、それぞれ異なるキーのセットに対して読み取り、書き込み、または反復処理を行う場合。それ以外の場合は、RWMutex を使用したマップの方がシンプルで、同等のパフォーマンスが得られます。
Q atomic はどのような場面に適していますか?
A 単純なカウンタ、ステータスフラグ、および CAS(Compare And Swap)による楽観的ロックです。atomicはミューテックスよりも10~100倍高速ですが、intuintpointerなどの基本型でのみ使用可能です。複雑なロジックの場合は、代わりにミューテックスを使用してください。
Q レース検出機能はどのように使えばいいですか?
A go run -race main.go または go test -race ./... を使用します。これは実行時にデータレースを検出します。2つのgoroutineが同時に同じ変数にアクセスし、そのうち少なくとも1つが書き込み操作である場合、警告が表示されます。CI/CDでは、この機能を有効にしておくことをお勧めします。
Q sync.Pool はどのような場合に使用すべきですか?
A 一時的なオブジェクト(JSON デコードや Protobuf メッセージ用のバッファなど)を頻繁に割り当てたり解放したりする必要がある場合です。このプールは、割り当てられたオブジェクトをキャッシュすることで、ガベージコレクタの負荷を軽減します。注:プール内のオブジェクトは、いつでもガベージコレクタによって回収される可能性があります。
Q ミューテックスは再入可能ですか?
A いいえ。Goのsync.Mutexは再入不可能なロックです。同じgoroutineがLockを連続して2回呼び出すと、デッドロックが発生します。同じゴルーチン内で再度ロックを取得する必要がある場合は、再帰呼び出しで sync.Mutex を使用するようにコードをリファクタリングするか、defer を使用してロックが適切に解放されるようにしてください。
Q LockとRLockの呼び出し順序は重要ですか?
A 非常に重要です。RWMutexでは、RLockの実行中にLockを呼び出すことはできません。これを行うとデッドロックが発生します。また、Lockの実行中にRLockを呼び出すこともできません(書き込み優先)。推奨事項:書き込み操作には Lock のみを、読み取り操作には RLock のみを使用し、これらを混在させないでください。

📖 まとめ


📝 練習問題

  1. 基本問題(難易度 ⭐):ミューテックスを使用して、map[string]int への並行読み取りおよび書き込みを保護してください。データを書き込むためのライター・ゴルーチン 10 個と、データを読み取るためのリーダー・ゴルーチン 10 個を起動し、レースコンディションが発生しないことを確認してください(-race テストに合格すること)。

  2. 上級問題(難易度 ⭐⭐):4種類のカウンタ(アトミック、ミューテックス、RWMutex、チャネル)のパフォーマンスを比較します。10,000回のインクリメント操作を実行する100個のgoroutineについて、所要時間を測定し、その正しさを検証してください。-race を使用して、データ競合が発生していないことを確認してください。

  3. 課題(難易度 ⭐⭐⭐)読み取りが多く、書き込みが少ない高並行性のキャッシュを実装してください。要件:(1) 保護に RWMutex を使用すること;(2) Get/Set/Delete/Range 操作をサポートすること;(3) TTL による有効期限切れをサポートすること(time.Now().After(expireAt)); (4) 書き込み操作中はすべての読み取り操作をブロックするが、並行した読み取り操作は許可すること;(5) -race テストに合格すること。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%