記事ディレクトリ
1はじめに
では前の記事、私は主に、相互排除との同期、クリティカルセクションや重要なリソース、およびその意味、特性および接続を含む、いくつかの概念の知識を、記述、およびPOSIXスレッド間の相互排他や同期のメカニズムをまとめました。この記事の主な内容は、最も単純な相互排除メカニズムと相互排他ロックについてです。
2ミューテックス
ミューテックス(Mutex)は、最も単純な種類の相互排除メカニズムです。相互排他ロックは、名前が示すようにロックです。相互排他とは、(スレッド)が同時にロック(ロック)を保持できないことを意味します。つまり、いつでも1つのスレッドだけがロックを保持し、他のスレッドがロックを保持する必要がある場合、他の保持スレッドがロックを解放(ロック解除)するのを待つ必要があります。
mutexロックを使用してスレッド間の相互排除を実現する原則は、メソッドをロックして共有リソースのアトミック操作を実現し、共有リソースの整合性を確保することです。常に1つのスレッドのみがロックを保持しているため、1つのスレッドのみが共有リソースにアクセスしており、他のスレッドはロックを申請できません。スレッドがミューテックスロックの適用に失敗すると、スレッドはアクティブにCPUリソースを放棄し、ミューテックスロックを適用して共有リソースにアクセスした後、スレッドはシステムによって停止されます。
2.1 Mutexの機能
- 同時にロックを保持することはできません
- ロックとロック解除は同じスレッドで操作する必要があります
- スレッドをスリープさせることができる、スレッドはミューテックスロック(他のスレッドによって占有されている)を申請できない、スレッドはスリープ状態に入る
2.2 mutexロックの適用シナリオ
- 複数のリソース共有
- クリティカルセクションのタスクが少ない(ロック時間が短い)
2.3 mutexロックの使用の原則
-
ロックの責任は単一であり、各ロックは1つの共有リソースのみをロックします
ミューテックスロックが複数の共有リソースを保護している場合、各共有リソースの一意性は保証されないため、各共有リソースがロックを備えていることを確認する必要があります。
-
ロック範囲は可能な限り小さく、ロック時間は短い
ロックの範囲は可能な限り小さく、重要な領域のみがロックされてロック時間が短縮されます。長期間のロックは、スレッドのシリアル時間を増やし、システムのスケジューリング効率を低下させます。したがって、ロック操作の原則は、共有リソースアクセスコード領域のみをロックし、スレッドの近くや関数の外ではなく、関数内のリソース操作に近い場所をロックし、共有リソースにアクセスした直後にロックを解除することです。
-
ネストされたロックをできるだけ使用しない
原則として、ネストされたロックを使用することはお勧めしません。ネストされたロックは、簡単にデッドロックを引き起こす可能性があります。ネストされたロックを使用する必要がある場合は、ロックとロック解除のネストシーケンスを確保する必要があります。そうしないと、デッドロックが発生しやすくなります。
-
コールバック関数のロックを回避する
可能な限り最小のロック範囲の原則に従って、デッドロックを回避する確率を減らします。
-
ロック内のジャンプステートメントを回避する
return、break、continue、goto
関数が戻るときに関数がロック解除されてデッドロックになるのを防ぐために、ロック範囲のジャンプステートメントに注意してください。
3 Mutexの使用
mutexロックを使用する基本的な手順は次のとおりです。
[1]ミューテックスロックインスタンスを作成する
[2] mutexを初期化します
[3]共有リソースにアクセスする前にロックする
[4]リソースにアクセスした直後にロックを解除します
[5] mutexロックを破棄します
3.1ミューテックスを作成する
posixスレッドmutex pthread_mutex_t
は、データ構造によって表されます。Mutexインスタンスは静的および動的に作成できます。
pthread_mutex_t mutex;
3.2 mutexを初期化する
相互排他ロックの初期化では、pthread_mutex_init
動的初期化またはマクロを PTHREAD_MUTEX_INITIALIZER
使用して静的初期化を実現できます。これPTHREAD_MUTEX_INITIALIZER
は、POSIXで定義されている構造定数です。
動的初期化
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
-
mutex
、mutexインスタンスのアドレスはNULLにできません -
attr
、mutexロック属性のアドレス、およびNULLはデフォルト属性を使用することを意味します。ほとんどの場合、デフォルト属性を使用できます。属性の詳細については、セクション4を参照してください。 -
戻り、成功した場合は0を返し、パラメーターが無効な場合はEINVALを返します。
静的初期化
マクロを使用したPTHREAD_MUTEX_INITIALIZER
静的初期化メソッドはpthread_mutex_init
、デフォルト属性(attr
着信NULL)を使用した動的初期化と同等ですが、PTHREAD_MUTEX_INITIALIZER
マクロは関連するエラーパラメータをチェックしないという違いがあります。
使用例:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
3.3 mutexロック(アプリケーションロック)のロック
ミューテックスロックは、ブロックモードと非ブロックモードに分かれており、一般的に使用されるモードは、一般的にブロックモードです。
3.3.1ブロッキングモードでのロック
int pthread_mutex_lock(pthread_mutex_t *mutex);
-
mutex
、mutexインスタンスのアドレスはNULLにできません -
返す
戻り値 解説 0 成功 EINVAL 無効な引数 EDEADLK ネストされていないロックは繰り返しロックに適用されます 如果互斥锁还没有被其他线程持有(上锁),则申请持有锁的线程获得锁。如果互斥锁已经被当前线程持有,且互斥锁属性设置的类型为嵌套锁,则该互斥锁的持有计数加 1,当前线程也不会挂起或者睡眠,线程必须根据嵌顺序依次解锁,否则造成死锁问题。如果互斥锁被其他线程持有,则当前线程将被阻塞,直到持有互斥锁的线程解锁后才唤醒继续执行,所有等待互斥锁的线程按照先进先出的原则获取互斥锁。
3.3.2 非阻塞上锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
mutex
,互斥锁实例地址,不能为NULL- 返回
返回值 | 描述 |
---|---|
0 | 成功 |
EINVAL | 参数无效 |
EDEADLK | 非嵌套锁重复申请锁 |
EBUSY | 锁被其他线程持有 |
调用该函数会立即返回,不会引起线程睡眠。实际应用可以根据返回状态执行不同的任务操作。
3.4 互斥锁释放
int pthread_mutex_unlock(pthread_mutex_t *mutex);
mutex
,互斥锁实例地址,不能为NULL- 返回
返回值 | 描述 |
---|---|
0 | 成功 |
EINVAL | 参数无效 |
EPERM | 非嵌套锁重复释放锁 |
EBUSY | 锁被其他线程持有 |
3.5 互斥锁销毁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
mutex
,互斥锁实例地址,不能为NULL- 返回
返回值 | 描述 |
---|---|
0 | 成功 |
EINVAL | mutex已被销毁过,或者mutex为空 |
EBUSY | 锁被其他线程持有 |
pthread_mutex_destroy
用于销毁一个已经使用动态初始化的互斥锁。销毁后的互斥锁处于未初始化状态,互斥锁的属性和控制块参数处于不可用状态。使用销毁函数需要注意几点:
- 已销毁的互斥锁,可以使用
pthread_mutex_init
重新初始化使用 - 不能重复销毁已销毁的互斥锁
- 使用宏
PTHREAD_MUTEX_INITIALIZER
静态初始化的互斥锁不能销毁 - 没有线程持有锁时,且该锁没有阻塞任何线程,才能销毁
3. 6 写个例子
代码实现功能:
- 创建两个线程
- 线程分别对全局变量访问,并输出到终端
- 期望结果,线程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_MUTEX 1 /* 是否使用互斥锁,使用,0不使用 */
#if USE_MUTEX
pthread_mutex_t mutex;
#endif
static int8_t g_count = 0;
void *thread0_entry(void *data)
{
uint8_t i =0;
#if USE_MUTEX
pthread_mutex_lock(&mutex);
#endif
for (i = 0;i < 5;i++)
{
g_count ++;
printf("%d ", g_count);
usleep(100);
}
#if USE_MUTEX
pthread_mutex_unlock(&mutex);
#endif
printf("\r\n");
}
void *thread1_entry(void *data)
{
uint8_t i =0;
#if USE_MUTEX
pthread_mutex_lock(&mutex);
#endif
for (i = 0;i < 5;i++)
{
printf("%d ", g_count);
g_count--;
usleep(100);
}
#if USE_MUTEX
pthread_mutex_unlock(&mutex);
#endif
printf("\r\n");
}
int main(int argc, char **argv)
{
pthread_t thread0,thread1;
void *retval;
#if USE_MUTEX
pthread_mutex_init(&mutex, NULL);
#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/mutex$ gcc mutex.c -o mutex -lpthread
acuity@ubuntu:/mnt/hgfs/LSW/STHB/pthreads/mutex$ ./mutex
1 1 0 0 0 0 1 1 1 1
使用互斥锁的结果
线程1持有锁之后,访问执行完后才释放锁,线程2申请到锁,输出结果正确。
acuity@ubuntu:/mnt/hgfs/LSW/STHB/pthreads/mutex$ gcc mutex.c -o mutex -lpthread
acuity@ubuntu:/mnt/hgfs/LSW/STHB/pthreads/mutex$ ./mutex
1 2 3 4 5
5 4 3 2 1
コードでは、usleep
関数はスレッドの同時実行をシミュレートするために使用されます。printf
関数がロックされるため、実際の使用は許可されず、ロックの原則に違反します。これは単なるシミュレーションシナリオテストです。
4 mutex属性
デフォルトのミューテックスプロパティを使用すると、ほとんどのアプリケーションシナリオに対応でき、ミューテックスプロパティを特別なシナリオで調整することもできます。ミューテックスの主なプロパティとAPIを以下に示します。mutexプロパティを設定する基本的な手順は次のとおりです。
[1] mutexロック属性のインスタンスを作成します
【2】初期プロパティインスタンス
【3】属性設定
[4]プロパティインスタンスの破棄
4.1 mutexロックを作成する
posixスレッドのミューテックスロック属性pthread_mutexattr_t
は、データ構造によって表されます。Mutex属性インスタンスは、静的および動的に作成できます。
pthread_mutexattr_t attr;
4.2 mutexロックプロパティの初期化と破棄
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
attr
、mutex属性インスタンスのアドレスはNULLにできません- 成功した場合は0を返し、パラメータが無効な場合はEINVALを返します
スレッド属性を設定するときは、最初に属性pthread_mutexattr_t
インスタンスを作成してpthread_mutexattr_init
から、関数の初期インスタンスを呼び出し、次に属性設定を呼び出します。初期化後の属性値は、デフォルトのミューテックス属性です。これはpthread_mutex_init
、デフォルトの属性でattr
初期化を使用することと同じです(NULLを渡します)。
4.3 Mutexスコープ
mutexロックのスコープは、ミューテックスのスコープを示します。これは、インPTHREAD_PROCESS_PRIVATE
プロセス(作成者)のスコープとクロスプロセスのスコープに分けられますPTHREAD_PROCESS_SHARED
。インプロセススコープは、インプロセススレッドの相互排除にのみ使用でき、クロスプロセスは、システムのすべてのスレッド間の相互排除に使用できます。
スコープ設定と取得機能:
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *attr, int *pshared);
attr
、mutex属性インスタンスのアドレスはNULLにできませんpshared
、スコープタイプ、PTHREAD_PROCESS_PRIVATE
およびPTHREAD_PROCESS_SHARED
- 成功した場合は0を返し、パラメータが無効な場合はEINVALを返します
4.4 mutexタイプ
mutexロックのタイプによって、ロックの申請時にスレッドが実行するアクションが決まります。一般的に使用されるmutexのタイプは次のとおりです。
PTHREAD_MUTEX_NORMAL
、通常のロック、デフォルトのタイプ。スレッドがロックを保持すると、他のスレッドがロックを申請し、ブロックおよび一時停止して待機キューを形成します。スレッドがロック解除された後、待機スレッドは「先入れ先出し」の原則に従ってロックを取得して、リソース割り当ての公平性を確保します。このタイプのロックはデッドロックを検出しません。スレッドがロック解除されていない場合、ロックを繰り返し適用して保持すると、デッドロックが発生します。PTHREAD_MUTEX_RECURSIVE
、ネストされたロック。スレッドは同じロックの保持を複数回申請できますが、ホルダーは入れ子の順序でロックを解除する必要があります。そうしないと、デッドロックが発生します。ネストされたロックを使用することは一般に推奨されておらず、それらを使用するときは注意が必要です。PTHREAD_MUTEX_ERRORCHEC
、エラー検出ロック。この同様のロックを変更すると、デッドロックを検出できます。ロックが解除されていない状態でスレッドが繰り返しロックの保持を申請すると、エラーコード(EDEADLK)が返され、ロックを繰り返し申請したときにデッドロックが発生しないことが保証されます。検出ロックを使用することをお勧めします。PTHREAD_MUTEX_DEFAULT
、他のタイプのロックは通常のロックと同様です。
ミューテックスタイプの設定と取得機能:
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);
int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type);
attr
、mutex属性インスタンスのアドレスはNULLにできませんtype
、ロックタイプ- 成功した場合は0を返し、パラメータが無効な場合はEINVALを返します
5まとめ
ミューテックスは最も単純な相互排除メカニズムであり、簡単に使用できますが、重要な問題、つまりデッドロックを引き起こすことも簡単です。デッドロックの問題により、プロセス全体またはシステム内のスレッドがロックを申請できず、スリープ状態に留まることができないため、システム内のスレッドが「枯渇」します。したがって、mutexロックを使用するときは注意して、ネストされたロックを使用しないようにし、エラー検出ロックを使用してください。mutexを使用する際の注意事項については、2.3節のmutexを使用する際の原則を参照してください。