記事ディレクトリ
1はじめに
前の記事では、mutexロックと読み取り/書き込みロックの意味、属性、使用原理、使用シナリオ、および使用方法を個別に説明しました。この記事では、相互排他ロックと読み取り/書き込みロック- スピンロック以外の3番目のタイプのロックについて説明します。
2スピンロック
スピンロックは、スレッド間の相互排除のメカニズムです。スピンロックは本質的にロックであり、その機能は相互排他ロックとまったく同じです。共有リソースへの相互排他的アクセスの目的を達成するために、いつでも1つのスレッドのみがロックを保持できます。唯一の違いは、2つのスケジューリング方法が異なることです。スレッドがミューテックスロックを適用できない場合、スレッドはスリープ状態になり、CPUリソースを放棄します。ミューテックスが取得された後、スレッドはウェイクアップして実行を継続しますが、スピンロックはブロックされません。スピンロックを取得するまで、スレッドをスリープさせ、CPUリソースを占有させます。スピンロックは軽量ロックの一種です。相互排他ロックと比較すると、リソースのオーバーヘッドは小さくなります。スピンロックは、非常に短時間でロックするのに最適であり、効率を向上させることができます。
2.1スピンロック機能
スピンロックの特性は、その命名と一致しています。スレッドがロックを取得できない場合、スレッドは常にビジー待機(スピンインプレース?)状態であり、CPUを占有している間はタスクを処理できません。スピンロックの特性上、ロック時間が極端に短いシーンに適しています。スピンロックを長期間使用するとシステム性能が低下します。リソースへのアクセスに時間がかかり、ロックを保持するのに長い時間がかかる場合は、他の相互排除メカニズムを検討する必要があります。
- スレッド相互排除用
- ブロッキングがCPUリソースを占有し続ける
- スレッドをスリープさせることはできません
- 軽量ロック
- 作成、保持、解放プロセスを含む、リソースのオーバーヘッドが少ない
2.2スピンロックの適用シナリオ
スピンロックはもともと、マルチコアプロセッサ(SMP)の同時実行が競合状態を引き起こすのを防ぐための相互排除メカニズムとして導入されました。スピンロックはユーザーモードではほとんど使用されませんが、カーネルモードでは、一般的なドライバー開発でスピンロックがよく使用されます。カーネルモードでのスピンロックの使用については、記事「同時実行性と競合(適切な保護メカニズムの選択方法)」を参照してください。スピンロックは、短時間での軽量ロックに適しています。
- Mutexリソースのアクセス時間は非常に短く(短いロック時間)、2つのコンテキストスイッチの時間よりも短い
- 特別なシナリオ、スレッドをハングアップさせたくない
2.3スピンロックの使用の原則
スピンロックはミューテックスと同じです。スピンロックを使用するという原則は、ミューテックスを使用するという原則を参照できます。ミューテックスを使用するという原則は、スピンロックを使用する基本的な原則でもあります。
- ロック時間は非常に短く、時間内にロックが解除されます
- ネストされた(再帰的な)アプリケーションがスピンロックを保持することを許可しないでください。そうしないと、デッドロックが発生します。
- 過度のスピンロックアプリケーションを回避し、CPUリソースの浪費を防ぐ
注意:
スピンロックの保持を申請すると、常にCPUを占有します。スピンロックがネストまたは再帰的に適用される場合、ロックが第2レベルで適用されると、ロックは第1レベルで保持されるため、第2レベルはロックを取得できず、常に待機状態になります。そして、CPUを占有すると、プログラムはロックを解放するために最外層にジャンプできなくなり、デッドロックが発生します。したがって、再帰プログラムでは注意してスピンロックを使用してください
3スピンロックの使用
スピンロックを使用するための基本的な手順は次のとおりです。
[1]スピンロックインスタンスを作成する
[2]スピンロックを初期化します
[3]スピンロックを保持する
[4]スピンロックを解除します
[5]スピンロックの例を破棄する
3.1スピンロックを作成する
posixスレッドスピンロックpthread_spinlock_t
は、データ構造で表されます。スピンロックインスタンスは、静的および動的に作成できます。
pthread_spinlock_t spinlock;
3.2スピンロックを初期化する
スピンロック初期化は、pthread_rwlock_init
関数を使用した動的初期化のみをサポートします。
int pthread_spin_init(pthread_spinlock_t *spinlock, int pshared);
-
spinlock
、スピンロックインスタンスアドレス、NULLにはできません -
pshared
、スピンロックスコープPTHREAD_PROCESS_PRIVATE
、インプロセス(作成者)スコープ、インプロセススレッドの相互排除にのみ使用できますPTHREAD_PROCESS_SHARED
、クロスプロセススコープ、システムのすべてのスレッド間の相互排除に使用 -
戻り、成功した場合は0を返し、パラメーターが無効な場合はEINVALを返します。
3.3スピンロックロック(アプリケーションロック)
スピンロックアプリケーションの保持は、ブロッキングモードと非ブロッキングモードに分けられ、一般的に使用されるモードは、一般的にブロッキングモードです。
3.3.1ブロッキング方法
int pthread_spin_lock(pthread_spinlock_t *spinlock);
-
spinlock
、スピンロックインスタンスアドレス、NULLにはできません -
戻り、成功した場合は0を返し、パラメーターが無効な場合はEINVALを返します。
スピンロックが他のスレッドによって保持(ロック)されていない場合、スピンロックを申請しているスレッドがロックを取得します。スピンロックが他のスレッドによって保持されている場合、スピンロックを保持しているスレッドがロック解除され、スレッドがロックを取得して実行を継続するまで、スレッドは(CPUを占有して)待機状態でした。スピンロックの再帰的なネストは許可されていません。それ以外の場合、デッドロックが発生します。
3.3.2ノンブロッキングモード
int pthread_spin_trylock(pthread_spinlock_t spinlock*);
spinlock
、スピンロックインスタンスアドレス、NULLにはできません- 返す
戻り値 | 解説 |
---|---|
0 | 成功 |
EINVAL | 無効な引数 |
EDEADLK | デッドロック |
忙しい | ロックは別のスレッドによって保持されています |
この関数を呼び出すと、待機をブロックせずにすぐに戻ります。実際のアプリケーションは、戻りステータスに基づいてさまざまなタスク操作を実行できます。
3.4スピンロック解除
int pthread_spin_unlock(pthread_spinlock_t *spinlock);
spinlock
、スピンロックインスタンスアドレス、NULLにはできません- 返す
戻り値 | 解説 |
---|---|
0 | 成功 |
EINVAL | 無効な引数 |
EDEADLK | デッドロック |
忙しい | ロックは別のスレッドによって保持されています |
スピンロックは、保持した後、時間内に解放する必要があり、ロックを複数回解放してはなりません。
3.5スピンロック破壊
int pthread_spinlock_destroy(pthread_spinlock_t *spinlock);
spinlock
、スピンロックインスタンスアドレス、NULLにはできません- 返す
戻り値 | 解説 |
---|---|
0 | 成功 |
EINVAL | スピンロックが破壊されたか、スピンロックが空です |
忙しい | スピンロックは他のスレッドで使用されています |
pthread_spinlock_destroy
動的に初期化されたスピンロックを破棄するために使用されます。破壊されたスピンロックは初期化されていない状態で、スピンロック属性と制御ブロックパラメーターは使用できない状態です。destroy関数を使用するときに注意すべき点がいくつかあります。
- 破壊されたスピンロックは
pthread_spinlock_init
再初期化できます - 破壊されたスピンロックを繰り返し破壊することはできません
- スピンロックを保持しているスレッドがない場合にのみ破棄できます
3.6例を書く
コード実現関数:
- 2つのスレッドを作成する
- 2つのスレッドがそれぞれグローバル変数にアクセスし、端末に出力する
- 期待される結果、スレッド1の出力結果 "1 2 3 4 5"、スレッド2の出力結果 "5 4 3 2 1"
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include "pthread.h"
#define USE_SPINLOCK 1 /* 是否使用自旋锁,使用,0不使用 */
#if USE_SPINLOCK
pthread_spinlock_t spinlock;
#endif
static int8_t g_count = 0;
void *thread0_entry(void *data)
{
uint8_t i =0;
#if USE_SPINLOCK
pthread_spin_lock(&spinlock);
#endif
for (i = 0;i < 5;i++)
{
g_count ++;
printf("%d ", g_count);
usleep(100);
}
printf("\r\n");
#if USE_SPINLOCK
pthread_spin_unlock(&spinlock);
#endif
}
void *thread1_entry(void *data)
{
uint8_t i =0;
usleep(10); /* 让线程0先执行 */
#if USE_SPINLOCK
pthread_spin_lock(&spinlock);
#endif
for (i = 0;i < 5;i++)
{
printf("%d ", g_count);
g_count--;
usleep(100);
}
printf("\r\n");
#if USE_SPINLOCK
pthread_spin_unlock(&spinlock);
#endif
}
int main(int argc, char **argv)
{
pthread_t thread0,thread1;
void *retval;
#if USE_SPINLOCK
pthread_spin_init(&spinlock, PTHREAD_PROCESS_PRIVATE);/* 进程内作用域 */
#endif
pthread_create(&thread0, NULL, thread0_entry, NULL);
pthread_create(&thread1, NULL, thread1_entry, NULL);
pthread_join(thread0, &retval);
pthread_join(thread1, &retval);
return 0;
}
スピンロックなしの結果
ロックは使用されず、スレッドは同時に実行され、「同時に」グローバル変数g_count
とprintf
出力にアクセスするため、実際の結果は期待どおりではありませんでした。
acuity@ubuntu:/mnt/hgfs/LSW/STHB/pthreads/spinlock$ gcc spinlock.c -o spinlock -lpthread
acuity@ubuntu:/mnt/hgfs/LSW/STHB/pthreads/spinlock$ ./spinlock
1 2 2 2 2 2 2 1 1 1
スピンロックを使用した結果
スレッド0がロックを保持した後、アクセスの実行後にロックが解放され、スレッド2がロックに適用され、出力結果は正しいものになります。
acuity@ubuntu:/mnt/hgfs/LSW/STHB/pthreads/spinlock$ gcc spinlock.c -o spinlock -lpthread
acuity@ubuntu:/mnt/hgfs/LSW/STHB/pthreads/spinlock$ ./spinlock
1 2 3 4 5
5 4 3 2 1
コードでは、printf
関数がロックされており、実際の使用は許可されていないため、ロックの原則に違反します。これは単なるシミュレーションシナリオのテストです。
4スピンロックのプロパティ
スピンロックは、その属性に「スコープ」が1つしかない軽量ロックですpthread_spin_init
。スコープは、関数が呼び出されてスピンロックを初期化するときに指定されます。スピンロックスコープは、スピンロックの相互排除スコープを表します。スピンロックスコープは、プロセス内(作成者)スコープPTHREAD_PROCESS_PRIVATE
とプロセス間スコープに分かれていますPTHREAD_PROCESS_SHARED
。インプロセススコープは、インプロセススレッドの相互排除にのみ使用でき、クロスプロセスは、システムのすべてのスレッド間の相互排除に使用できます。
5まとめ
スピンロックによって実装される関数は、スレッド間の相互排他アクセスに使用されるミューテックスロックと同じです。スピンロックは、スレッドスリープを引き起こさない軽量のロックです。ロック時間が非常に短いシナリオに適しています。そのリソースオーバーヘッドはミューテックスのオーバーヘッドよりも低いため、非常に短いロックシナリオでのスピンロックの使用効率はより高い。ミューテックスの記事のセクション2.3の「ミューテックスロックの使用の原則」と組み合わせたスピンロックの使用に関する注意事項は、セクション2.3の「スピンロックの使用の原則」を参照してください。以上で、相互排他ロック、読み書きロック、スピンロックの説明は終了ですが、比較のため、3つの特性の違いを以下に示します。
相互排他ロック、読み取り/書き込みロック、スピンロックの比較
主な特徴 | スレッドをスリープさせる | 適用範囲 | リソースコスト | |
---|---|---|---|---|
ミューテックス | 相互に排他的 | はい | 一般的な排他的アクセス | 普通 |
読み書きロック | 共有を読む | はい | もっと読んで、もっと書く | 普通 |
スピンロック | スピン待ち | 番号 | 非常に短いロック時間 | 低オーバーヘッド |