1はじめに
前の記事で説明したミューテックス、読み取り/書き込みロック、およびスピンロックは、共有リソースを保護するための相互排他に使用され、バリアは、複数のスレッドの同期に使用され、条件変数はスレッドの同期に使用されます。バリアはスレッド間で情報を受け渡すことができず、条件変数には信号損失のリスクがあります(もちろん、それを回避するための対策を追加できます)。この記事では、別のスレッド同期メカニズム-セマフォについて説明します。以前の同期(相互排除)メカニズムと比較して、セマフォはより柔軟で信頼性が高く、信号損失の可能性はありません。「生産者-消費者」アプリケーションモデルでよく使用されます。
2セマフォ
POSIXセマフォ(Seamphore)は、プロセスとスレッド間の同期メカニズムです。セマフォは基本的に、使用可能なリソースの数を記録するために使用される負でない整数変数です。セマフォの操作は、スレッドの相互排除または同期を実現する整数変数に対するアトミック操作です。プラス1を実行するために利用可能なリソースを追加することは、Vオペレーションとも呼ばれ、リソースを取得した後、マイナス1を実行することは、Pオペレーションとも呼ばれます。リソースカウントが0の場合、リソースが利用できず、リソースを取得しているスレッドはブロックされ、セマフォが利用可能になるまで中断され、実行のために起動されます。セマフォは変数を使用して信号値を計算するため、待機中のセマフォスレッドの準備ができていなくても、条件変数のような信号が失われる可能性はありません。
セマフォは、信号値(使用可能なリソースの数)に応じて、バイナリセマフォとカウントセマフォに分割できます。
- バイナリセマフォ、セマフォ値は0と1のみ、初期値は1、1はリソースが利用可能、0はリソースが利用不可、バイナリセマフォはミューテックスに類似
- セマフォをカウントします。セマフォの値は0と1より大きい制限値の間にあり、信号値は使用可能なリソースの数を示します
セマフォは、さまざまなオブジェクトに応じて、名前付きセマフォと名前なしセマフォに分けることができます。
- よく知られているセマフォ、信号値はファイルに保存され、プロセス間同期に使用されます
- メモリベースのセマフォとしても知られている名前のないセマフォ、信号値はスレッド間の同期のためにメモリに保存されます
2.1セマフォの特性
- スレッド同期に使用
- スレッドをスリープ状態にする可能性があります。使用可能なセマフォがないため、スレッドはスリープ状態になります
- 持続的
- 信号損失なし
2.2セマフォの適用可能なシナリオ
セマフォで使用されるシナリオは、通常、「生産者-消費者」モデルです。プロデューサスレッドとコンシューマスレッドは同じメモリブロックにアクセスし、プロデューサスレッドは共有メモリにデータを書き込み、コンシューマスレッドは共有メモリからデータを読み取り、2つのスレッドはセマフォを介して同期されます。一般的なシナリオは次のとおりです。
-
シリアル(USB、UART)データ受信および処理プロセス
-
データ非同期出力(端末、ファイル)
-
ファイルの読み書き
2.3セマフォの使用の原則
- なし
3セマフォの使用
セマフォを使用するための基本的な手順は次のとおりです。
[1]セマフォインスタンスを作成する
[2]セマフォを初期化します
[3]セマフォP操作
[4]セマフォV操作
[5]セマフォを破壊する
3.1セマフォを作成する
posixセマフォはsem_t
データ構造で表されます。名前なしセマフォと名前付きセマフォの作成は少し異なります。
3.1.1名前のないセマフォの作成
名前なしセマフォインスタンスは、静的および動的に作成できます。
sem_t sem;
3.1.2有名なセマフォを作成する
既知のセマフォである場合は、セマフォポインタを定義するだけで、セマフォインスタンスは名前付きセマフォ関数によって作成されますsem_open
。
sem_t *sem_open(const char *name,int oflag, mode_t mode, unsigned int value);
name
、セマフォ名oflag
、ロゴ、入ってくるロゴに基づいて作成されたセマフォを作成または開きますmode
、アクセス許可value
、セマフォの初期値
ロゴ | 意味 |
---|---|
O_CREAT | セマフォを作成する |
O_CREAT | O_EXCL | セマフォが存在しない場合は作成し、すでに存在する場合はNULLを返します |
0 | セマフォが存在しない場合はNULLを返します |
有名なセマフォの疑似コードを作成します。
sem_t *psem;
psem = sem_open(SEM_NAME, O_CREAT, 0555, 0);
3.2セマフォを初期化する
セマフォの初期化は、名前のないセマフォと既知のセマフォに分けられ、それらの初期化機能は異なります。
3.2.1名前のないセマフォの初期化
int sem_init(sem_t *sem, int pshared, unsigned int value);
-
sem
、セマフォインスタンスアドレス、NULLにはできません -
pshared
、セマフォスコープ、インプロセススコープPTHREAD_PROCESS_PRIVATE
とクロスプロセススコープに分かれていますPTHREAD_PROCESS_SHARED
-
value
、セマフォの初期値 -
戻り、成功は0を返し、失敗は-1を返し、エラーコードはに格納されます
error
。一般的なエラーは次のとおりです。[1]無効なパラメーター(EINVAL)
[2]
value
最大を超えましたSEM_VALUE_MAX
[3]プロセス間スコープを設定しますが、現在のシステムはそれをサポートしていません
[4] ENOSYS
3.2.2有名なセマフォの初期化
既知のセマフォの初期化は、通常sem_open
、追加の初期化なしで、関数を呼び出してそれを作成することによって行われます。sem_open
関数の使用については、セクション3.1.2を参照してください。
3.3セマフォを取得する
int sem_getvalue(sem_t *sem, int *sval);
sem
、セマフォインスタンスアドレス、NULLにはできませんsvval
、戻り信号値のアドレスを保存します。NULLにはできません- 0を返すと成功、それ以外の場合は-1が返され、エラーコード
error
が
sem_getvalue
関数を使用して現在のセマフォ値 を取得します。これは、使用可能なセマフォを判断するために使用できます。
3.4セマフォ待ち(Pオペレーション)
セマフォ待機モードはブロッキングモードと非ブロッキングモードに分けられ、ブロッキングモードは無限ブロッキングモードと指定タイムアウトブロッキングモードに分けられます。セマフォを待つのはP操作であり、セマフォを獲得すると信号値が1つ減ります。セマフォのP操作はアトミックです。
3.4.1ブロッキング方法
int sem_wait(sem_t *sem);
sem
、セマフォインスタンスアドレス、NULLにはできません- 0を返すと成功、それ以外の場合は-1が返され、エラーコード
error
が
この関数は、ブロッキングモードでセマフォを待ちます。シグナル値が0より大きい場合、シグナル値は1だけ減らされ、関数が返されます。シグナル値が0に等しい場合、リソースは利用できず、セマフォが利用可能になるまで呼び出しスレッドがブロックされ、一時停止され、実行のために起動されます。セマフォを待機するスレッドは、公平性を確保するために先入れ先出しの原則に従います。
3.4.2タイムアウト時間ブロッキングモードの指定
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
sem
、セマフォインスタンスアドレス、NULLにはできませんabstime
、タイムアウト時間、単位はクロックビート- 0を返すと成功、それ以外の場合は-1が返され、エラーコード
error
が
この関数は無期限に待機せず、指定されたクロックビートの後、使用可能なセマフォ関数がを返すまで待機せずETIMEDOUT
、スレッドは実行のために起動されます。
3.4.3ノンブロッキングモード
int sem_trywait(sem_t *sem);
sem
、セマフォインスタンスアドレス、NULLにはできません- 0を返すと成功、それ以外の場合は-1が返され、エラーコード
error
が
この関数は、セマフォが利用できない場合でも、ブロックせずにすぐに戻ります。
3.5セマフォの生成(V操作)
int sem_post(sem_t *sem);
sem
、セマフォインスタンスアドレス、NULLにはできません- 0を返すと成功、それ以外の場合は-1が返され、エラーコード
error
が
セマフォを待機しているスレッドがある場合は、待機中のスレッドが実行のために起動され、セマフォを待機しているスレッドがない場合は、シグナル値に1を加えたものが実行されます。これはV操作です。セマフォのP操作とV操作はどちらもアトミックであり、競合状態を引き起こしません。
3.6セマフォの破壊
セマフォの破棄は、名前のないセマフォと既知のセマフォに分けられ、それらの破棄機能は異なります。
3.6.1名前のないセマフォの破壊
int sem_destroy(sem_t *sem);
sem
、セマフォインスタンスアドレス、NULLにはできません- 0を返すと成功、それ以外の場合は-1が返され、エラーコード
error
が
sem_destroy
初期化された名前のないセマフォを破棄するために使用されます。破壊されたセマフォは初期化されていない状態にあり、セマフォのプロパティと制御ブロックのパラメータは使用できない状態にあります。destroy関数を使用するときに注意すべき点がいくつかあります。
- 破棄された名前のないセマフォは
sem_init
再初期化できます - 破棄された名前のないセマフォは繰り返し破棄できません
- 名前のないセマフォを保持しているスレッドがなく、セマフォがスレッドをブロックしていない場合は、破棄することができます。
3.6.2有名なセマフォの破壊
よく知られているセマフォの破壊には2つのステップが含まれます。最初のステップはセマフォを閉じ、次にセマフォを分離する(削除する)ことです。セマフォを破棄するために分離関数を呼び出すと、セマフォ名のみが削除され、カーネルは、セマフォを保持するすべてのプロセスがセマフォを閉じた後でのみ、セマフォを破棄します。
【1】有名なセマフォを閉じる
int sem_close(sem_t *sem);
name
、有名なセマフォ名- 0を返すと成功、それ以外の場合は-1が返され、エラーコード
error
が
プロセスが終了すると、プロセスがアクティブに終了しているか非アクティブに終了しているかに関係なく、プロセスは占有しているセマフォに対してこのシャットダウン操作を実行します。この関数は、シャットダウンセマフォ操作を実行するために呼び出されます。
[2]有名なセマフォの分離
int sem_unlink(const char *name);
name
、有名なセマフォ名- 0を返すと成功、それ以外の場合は-1が返され、エラーコード
error
が
sem_unlink
関数は、指定されたセマフォ名をすぐに削除します。この時点では、セマフォ自体は削除されず、セマフォを保持するプロセスは、セマフォを保持するすべてのプロセスがセマフォを閉じるまで、通常どおりセマフォを使用できます。実際にセマフォを削除します。
注意:
プロセスが有名なセマフォを作成すると、「/ dev / shm」ディレクトリに「sem.xxx」ファイルが生成されます。プロセス
open
にセマフォがない場合は、セマフォファイルの参照カウントが増加します。sem_unlink
関数が呼び出されると、「/ dev / shm」ディレクトリの「sem.xxx」ファイルがすぐに削除され、セマフォファイルの参照カウントがチェックされます。参照カウントが0の場合、セマフォは削除されます。それ以外の場合は、参照カウントが0になるまで待機します。セマフォを削除します。
3. 7例を書く
コード実現関数:
- 「生産者-消費者」モデルを作成する
- 2つのスレッドを作成します。1つはデータの生成を担当し、もう1つはデータを読み取って端末に出力します。
- 2つのスレッドがセマフォによって同期されます
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include "pthread.h"
struct _buff_node
{
uint8_t buf[64];
uint32_t occupy_size;
};
/* 信号量 */
sem_t sem;
/* 共享缓存 */
static struct _buff_node s_buf;
void *thread0_entry(void *data)
{
/* 消费者线程 */
uint8_t i =0;
for (;;)
{
sem_wait(&sem);
if (s_buf.occupy_size != 0)
{
printf("thread0 read data:");
for (i=0; i<s_buf.occupy_size; i++)
{
printf("0x%02x ", s_buf.buf[i]);
}
printf("\r\n");
}
}
}
void *thread1_entry(void *data)
{
/* 生产者线程 */
uint8_t i =0;
for (;;)
{
s_buf.occupy_size = 0;
for (i = 0;i<8; i++)
{
s_buf.buf[i] = rand();
s_buf.occupy_size++;
}
printf("thread1 write %dByte data\n", s_buf.occupy_size);
sem_post(&sem);
sleep(1); /* 1秒产生数据 */
}
}
int main(int argc, char **argv)
{
pthread_t thread0,thread1,thread2;
void *retval;
sem_init(&sem, PTHREAD_PROCESS_PRIVATE, 0);
pthread_create(&thread0, NULL, thread0_entry, NULL);
pthread_create(&thread1, NULL, thread1_entry, NULL);
pthread_join(thread0, &retval);
pthread_join(thread1, &retval);
return 0;
}
出力結果
acuity@ubuntu:/mnt/hgfs/LSW/STHB/pthreads/sem$ gcc sem.c -o sem -lpthread
acuity@ubuntu:/mnt/hgfs/LSW/STHB/pthreads/sem$ ./sem
thread1 write 8Byte data
thread0 read data:0x67 0xc6 0x69 0x73 0x51 0xff 0x4a 0xec
thread1 write 8Byte data
thread0 read data:0x29 0xcd 0xba 0xab 0xf2 0xfb 0xe3 0x46
4セマフォ属性
4.1セマフォのタイプ
セマフォは、さまざまなオブジェクトに応じて、名前付きセマフォと名前なしセマフォに分けることができます。
- プロセス間同期に使用される有名なセマフォ
- スレッド間の同期に使用される名前なしセマフォ
4.2名前のないセマフォスコープ
名前のないセマフォは、スレッド間の同期に使用され、他のスレッド同期(相互排他)メカニズムと同様に、スコープを持っています。名前のないセマフォスコープは、セマフォのスコープを示します。これは、インプロセス(作成者)スコープPTHREAD_PROCESS_PRIVATE
とプロセス間スコープに分かれていますPTHREAD_PROCESS_SHARED
。インプロセススコープはプロセス内のスレッド同期にのみ使用でき、プロセス間はシステムのすべてのスレッド間の同期に使用できます。
名前のないセマフォスコープは、sem_init
関数の初期化を呼び出すときにパラメータを指定しますpshared
。
4.3セマフォの永続性
永続性とは、セマフォのライフサイクルの範囲を指します。名前付きセマフォの永続性は、名前なしセマフォの永続性とは異なり、名前なしセマフォの永続性はスコープに関連しています。
- 有名なセマフォはカーネルに続きます。名前付きセマフォが作成されると、現在セマフォを開いているプロセスがなくても、カーネルが再ブートストラップするか
sem_unlink
、セマフォを削除する関数を呼び出すまで、その値は残ります。
- 名前のないセマフォのスコープはプロセス内にあります。この時点では、セマフォはプロセスと継続しています。セマフォは、作成プロセスが終了して終了すると破棄されます。
- 名前のないセマフォのスコープはプロセス間(システム内)です。このとき、セマフォは常に共有に存在するため、セマフォはカーネルと連続しています。
5まとめ
セマフォはプロセスとスレッド間の同期のメカニズムであり、既知のセマフォはプロセスの同期に使用され、名前のないセマフォはスレッド間の同期に使用されます。セマフォは、ミューテックス、読み書きロック、スピンロック、条件変数、バリアと同じです。セマフォはすべて、共有リソースを保護する機能を持っています。共有リソースのスレッドは相互に排他的なアクセス権を持っています。同時に、セマフォは「情報を渡す」機能も持っています。スレッド間で同期をとります。セマフォには信号が失われないという信頼性があり、「生産者-消費者」アプリケーションモデルでよく使用されます。