私の連絡先情報
郵便メール:
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
目次
2. Go言語での値転送とアドレス転送(参照転送)の使い方は?違いは何ですか?
3. Go 言語における配列とスライスの違いは何ですか? Go言語で渡すときの配列とスライスの違いは何ですか? Go 言語はスライス拡張をどのように実装しますか?
4.Go Conveyとは何ですか?一般的には何に使用されますか?
6.Go スライスの基盤となる実装は? スライス拡張メカニズムを実行しますか?
相違点: 1. Make は、slice、map、および chan タイプのデータの割り当てと初期化にのみ使用できますが、new は任意のタイプのデータを割り当てることができます。
2. 新しい割り当ては型「*Type」であるポインタを返しますが、make は型である参照を返します。
3. new によって割り当てられたスペースは、make がスペースを割り当てた後にクリアされ、初期化されます。
値渡しでは、パラメーターの値がコピーされ、対応する関数に入力されるだけであり、2 つの変数のアドレスは異なるため、相互に変更することはできません。
アドレス渡し(参照渡し)では変数そのものを対応する関数に渡し、関数内で変数の値の内容を変更することができます。
配列:
配列の固定長 配列の長さは配列型の一部であるため、[3]int と [4]int は 2 つの異なる配列型です。指定しない場合、サイズは配列型に基づいて自動的に計算されます。初期化ペアを値で渡すことはできません。
スライス:
スライスの長さは変更できます。スライスは、ポインタ、長さ、容量の 3 つの属性でサイズを指定する必要はありません。スライスはアドレスによって渡され (参照渡し)、配列または組み込み関数 make() を通じて初期化でき、初期化中に len=cap が展開されます。
配列は値型であり、スライスは参照型です。
配列の長さは固定されていますが、スライスは固定されていません (スライスは動的配列です)。
スライスの下層は配列です
Go 言語のスライス拡張メカニズムは非常に巧妙で、基になる配列を再割り当てし、データを新しい配列に移行することで拡張を実現します。具体的には、スライスを拡張する必要がある場合、Go 言語は新しい基になる配列を作成し、元の配列のデータを新しい配列にコピーします。次に、スライス ポインタは新しい配列を指し、長さは元の長さに拡張された長さを加えた値に更新され、容量は新しい配列の長さに更新されます。
スライスの拡張を実現するには:
1.17 へ
- // src/runtime/slice.go
-
- func growslice(et *_type, old slice, cap int) slice {
- // ...
-
- newcap := old.cap
- doublecap := newcap + newcap
- if cap > doublecap {
- newcap = cap
- } else {
- if old.cap < 1024 {
- newcap = doublecap
- } else {
- // Check 0 < newcap to detect overflow
- // and prevent an infinite loop.
- for 0 < newcap && newcap < cap {
- newcap += newcap / 4
- }
- // Set newcap to the requested cap when
- // the newcap calculation overflowed.
- if newcap <= 0 {
- newcap = cap
- }
- }
- }
-
- // ...
-
- return slice{p, old.len, newcap}
- }
メモリ領域を割り当てる前に、実行時に、スライスの現在の容量に基づいて、さまざまな拡張戦略を選択することができます。
予想容量が現在の容量の 2 倍より大きい場合は、現在のスライスの長さが 1024 未満の場合は予想容量が使用され、現在のスライスの長さが 1024 以上の場合は容量が 2 倍になります。 1024、新しい容量が予想容量を超えるまで、容量は毎回 25% ずつ増加します。
1.18 へ
- // src/runtime/slice.go
-
- func growslice(et *_type, old slice, cap int) slice {
- // ...
-
- newcap := old.cap
- doublecap := newcap + newcap
- if cap > doublecap {
- newcap = cap
- } else {
- const threshold = 256
- if old.cap < threshold {
- newcap = doublecap
- } else {
- // Check 0 < newcap to detect overflow
- // and prevent an infinite loop.
- for 0 < newcap && newcap < cap {
- // Transition from growing 2x for small slices
- // to growing 1.25x for large slices. This formula
- // gives a smooth-ish transition between the two.
- newcap += (newcap + 3*threshold) / 4
- }
- // Set newcap to the requested cap when
- // the newcap calculation overflowed.
- if newcap <= 0 {
- newcap = cap
- }
- }
- }
-
- // ...
-
- return slice{p, old.len, newcap}
- }
-
以前のバージョンとの違いは主に、拡張しきい値と次のコード行にあります。newcap += (newcap + 3*threshold) / 4
。
メモリ領域を割り当てる前に、実行時に、スライスの現在の容量に基づいて、さまざまな拡張戦略を選択することができます。
予想される容量が現在の容量の 2 倍より大きい場合は、予想される容量が使用されます。
現在のスライスの長さがしきい値 (デフォルトは 256) より短い場合、容量は 2 倍になります。
現在のスライスの長さがしきい値 (デフォルトは 256) 以上である場合、ベンチマークは毎回 25% ずつ増加します。 newcap + 3*threshold
新しい容量が予想される容量よりも大きくなるまで。
スライスの拡張は、go1.18 の前後で 2 つの段階に分かれています。
1. go1.18 の前:
予想される容量が現在の容量の 2 倍より大きい場合は、予想される容量が使用されます。
現在のスライスの長さが 1024 未満の場合、容量は 2 倍になります。
現在のスライスの長さが 1024 より大きい場合、新しい容量が予想される容量を超えるまで、容量は 25% ずつ増加します。
2. go1.18 以降:
予想される容量が現在の容量の 2 倍より大きい場合は、予想される容量が使用されます。
現在のスライスの長さがしきい値 (デフォルトは 256) より短い場合、容量は 2 倍になります。
現在のスライスの長さがしきい値 (デフォルトは 256) 以上である場合、ベンチマークは毎回 25% ずつ増加します。 newcap + 3*threshold
新しい容量が予想される容量よりも大きくなるまで。
go transfer は、golang をサポートする単体テスト フレームワークです。
go keep は、ファイルの変更を自動的に監視してテストを開始し、テスト結果をリアルタイムで Web インターフェイスに出力できます。
go transfer は、テスト ケースの作成を簡素化するための豊富なアサーションを提供します
遅延の機能は次のとおりです。
通常の関数またはメソッドを呼び出す前にキーワード defer を追加するだけで、defer に必要な構文が完成します。 defer ステートメントが実行されると、defer に続く関数が延期されます。 defer 文を含む関数が return で正常終了するかパニックで異常終了するかに関わらず、defer 文を含む関数が実行されるまでは defer 以降の関数は実行されません。関数内で複数の defer ステートメントを宣言とは逆の順序で実行できます。
延期の一般的なシナリオ:
defer ステートメントは、ロックのオープン、クローズ、接続、切断、ロック、解放などのペアの操作を処理するためによく使用されます。
遅延メカニズムを使用すると、関数ロジックがどれほど複雑であっても、どの実行パスでもリソースが確実に解放されます。
スライシングは配列に基づいて実装されます。その基礎となる層は配列自体が非常に小さいため、基礎となる配列の抽象化として理解できます。配列に基づいて実装されているため、基礎となるメモリが継続的に割り当てられ、インデックスを通じてデータを取得することもでき、反復処理やガベージ コレクションの最適化も可能です。 スライス自体は動的配列または配列ポインターではありません。内部的に実装されたデータ構造は、ポインターを介して基礎となる配列を参照し、データの読み取りおよび書き込み操作を指定された領域に制限するように関連する属性が設定されます。スライス自体は読み取り専用オブジェクトであり、その動作メカニズムは配列ポインターのカプセル化に似ています。
スライス オブジェクトは、フィールドが 3 つしかないデータ構造であるため、非常に小さいです。
基礎となる配列へのポインタ
スライスの長さ
スライス容量
Go (1.17) でのスライス拡張の戦略は次のとおりです。
最初に判断し、新しく適用された容量が古い容量の 2 倍を超える場合、最終的な容量は新しく適用された容量になります。
それ以外の場合、古いスライスの長さが 1024 未満の場合、最終的な容量は古い容量の 2 倍になります。
それ以外の場合、古いスライス長が 1024 以上の場合、最終容量は、最終容量が新しく要求された容量以上になるまで、古い容量から 1/4 ずつ周期的に増加します。
最終容量計算値がオーバーフローした場合、最終容量は新たに要求された容量となります。
状況 1:
元の配列にはまだ拡張可能な容量があります (実際の容量はまだいっぱいになっていません)。この場合、拡張された配列は依然として元の配列を指します。スライスの操作は、同じアドレスを指す複数のポインターに影響を与える可能性があります。スライス。
状況 2:
配列の容量が最大値に達していることがわかります。容量を拡張したい場合、Go はデフォルトでまずメモリ領域を開き、元の値をコピーしてから append() 操作を実行します。この状況は、元の配列にはまったく影響しません。 スライスをコピーするには、コピー機能を使用するのが最善です。
slice底层结构并没有使用加锁等方式,不支持并发读写,所以并不是线程安全的, 使用多个 goroutine 对类型为 slice 的变量进行操作,每次输出的值大概率都不会一样,与预期值不一致; slice在并发执行中不会报错,但是数据会丢失 如果想实现slice线程安全,有两种方式: 方式一:通过加锁实现slice线程安全,适合对性能要求不高的场景。
- func TestSliceConcurrencySafeByMutex(t *testing.T) {
- var lock sync.Mutex //互斥锁
- a := make([]int, 0)
- var wg sync.WaitGroup
- for i := 0; i < 10000; i++ {
- wg.Add(1)
- go func(i int) {
- defer wg.Done()
- lock.Lock()
- defer lock.Unlock()
- a = append(a, i)
- }(i)
- }
- wg.Wait()
- t.Log(len(a))
- // equal 10000
- }
-
方式二:通过channel实现slice线程安全,适合对性能要求高的场景。
- func TestSliceConcurrencySafeByChanel(t *testing.T) {
- buffer := make(chan int)
- a := make([]int, 0)
- // 消费者
- go func() {
- for v := range buffer {
- a = append(a, v)
- }
- }()
- // 生产者
- var wg sync.WaitGroup
- for i := 0; i < 10000; i++ {
- wg.Add(1)
- go func(i int) {
- defer wg.Done()
- buffer <- i
- }(i)
- }
- wg.Wait()
- t.Log(len(a))
- // equal 10000
- }