Linux環境でのアプリケーションプログラミング(4):マルチスレッドプログラミング

1つ:スレッドの概念

       スレッド、オペレーティングシステムがスケジュールできる最小単位。典型的なUNIXプロセスは、1つの制御スレッドのみと見なすことができます。プロセスは特定の時間に1つのことしか実行できず、複数の制御スレッドを使用すると、各スレッドは特定の時間に独自のタスクを独立して処理できます。この方法には多くの利点があります。

  • イベントタイプごとに個別の処理スレッドを割り当てることにより、非同期イベントを処理するためのコードを簡略化できます。各スレッドは、イベントを処理するときに同期プログラミングモードを使用できます。同期プログラミングモードは、非同期プログラミングモードよりもはるかに単純です。
  • このプロセスとの違いは、複数のスレッドが同じストレージアドレス空間とファイル記述子に自動的にアクセスできることです。
  • 一部の問題は、プログラム全体のスループットを向上させるために、マルチスレッド実行に分解できます。
  • 対話型プログラムは、複数のスレッドを使用することで応答時間を改善することもできます。

2:スレッドAPI

1.スレッドID

スレッドIDは、pthread_tデータ型で表されます。スレッドでは、pthread_self関数を呼び出すことで独自のスレッドIDを取得できます

#include <pthread.h>

pthread_t pthread_self(void);

2.スレッドの作成

#include <pthread.h>

int pthread_create(pthread_t *thread, 
                    const pthread_attr_t *attr,
                    void *(*start_routine) (void *), 
                    void *arg);
返回值:成功,返回0;否则返回错误编号

3.スレッドの終了

単一のスレッドは、次の3つの方法で終了できます。

  • スレッドは起動ルーチンから簡単に戻ることができ、戻り値はスレッドの終了コードです。
  • スレッドは、同じプロセス内の他のスレッドによってキャンセルできます。
  • スレッドはpthread_exitを呼び出します

この関数はスレッド終了関数です。終了するときに、void *タイプのデータをメインスレッドに渡すことができます。データを送信しないことを選択した場合は、パラメーターにNULLを入力できます。

プロセス内の他のスレッドも、pthread_join関数を呼び出すことでこのポインターにアクセスできます。

#include <pthread.h>
void pthread_exit(void *retval);

 

この関数はスレッド回復関数であり、スレッドが正常に回復されてブロックされるまで、デフォルトの状態はブロック状態です。最初のパラメーターはリサイクルされるスレッドのtid番号であり、2番目のパラメーターはスレッドがリサイクルされた後にスレッドによって送信されるデータです。

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
成功:返回0

この関数は非ブロッキングモードのリサイクル関数であり、戻り値によってスレッドをリサイクルするかどうかを判断します。リサイクルが成功すると、0を返します。残りのパラメーターはpthread_joinと一致します。

#include <pthread.h>
int pthread_tryjoin_np(pthread_t thread, void **retval);
成功:返回0

スレッドは、thread_cancel関数を呼び出すことにより、同じプロセス内の他のスレッドのキャンセルを要求することもできます。

#include <pthread.h>
int pthread_cancel(pthread_t thread);
成功:返回0

4.例

#define _GNU_SOURCE 
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>

void *fun1(void *arg)
{
	printf("Pthread:1 come!\n");
	while(1){
		sleep(1);
	}
}

void *fun2(void *arg)
{
	printf("Pthread:2 come!\n");
	pthread_cancel((pthread_t )(long)arg);
	pthread_exit(NULL);
}

int main()
{
	int ret,i,flag = 0;
	void *Tmp = NULL;
	pthread_t tid[2];
	ret = pthread_create(&tid[0],NULL,fun1,NULL);
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	sleep(1);
	ret = pthread_create(&tid[1],NULL,fun2,(void *)tid[0]);
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	while(1){
		for(i = 0;i <2;i++){
			if(pthread_tryjoin_np(tid[i],NULL) == 0){
				printf("Pthread : %d exit !\n",i+1);
				flag++;	
			}
		}
		if(flag >= 2) break;
	}
	return 0;
}

3:スレッド同期制御

       マルチスレッドには、重要なリソースをめぐる競合の問題があります。この問題を解決するために、スレッドは相互排除ロックを導入して重要なリソースへのアクセスを解決します。リソースをロックすることによる重要なリソースの保護は、単一のスレッドでのみ操作できます。操作が完了すると、残りのスレッドは操作する権利を取得できます。

1.ミューテックスを初期化します

#include <pthread.h>
int pthread_mutex_init(phtread_mutex_t *mutex, const pthread_mutexattr_t *restrict attr);
成功:返回0

ミューテックスを初期化するには、マクロを呼び出してすばやく初期化することもできます。

pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER;

2.ミューテックスを破棄します

#include <pthread.h>
int pthread_mutex_destory(pthread_mutex_t *mutex);
成功:返回0

3.ミューテックスのロック/ロック解除

ミューテックスがロックされている場合、ミューテックスのロックが解除されるまで、呼び出し元のスレッドはブロックされます。

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

スレッドをブロックしたくない場合は、次の関数を使用してミューテックスをロックしてみてください。

#include <pthread.h>
int pthread_mutex_trylock(pthread_mutex_t *mutex);
成功:返回0

4.例

#define _GNU_SOURCE 
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>

pthread_mutex_t mutex;

int Num = 0;

void *fun1(void *arg)
{
	pthread_mutex_lock(&mutex);
	while(Num < 3){
		Num++;
		printf("%s:Num = %d\n",__FUNCTION__,Num);
		sleep(1);
	}
	pthread_mutex_unlock(&mutex);
	pthread_exit(NULL);
}

void *fun2(void *arg)
{
	pthread_mutex_lock(&mutex);
	while(Num > -3){
		Num--;
		printf("%s:Num = %d\n",__FUNCTION__,Num);
		sleep(1);
	}
	pthread_mutex_unlock(&mutex);
	pthread_exit(NULL);
}

int main()
{
	int ret;
	pthread_t tid1,tid2;
	ret = pthread_mutex_init(&mutex,NULL);
	if(ret != 0){
		perror("pthread_mutex_init");
		return -1;
	}
	ret = pthread_create(&tid1,NULL,fun1,NULL);
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	ret = pthread_create(&tid2,NULL,fun2,NULL);
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);
	pthread_mutex_destroy(&mutex);
	return 0;
}

4:スレッド実行順序制御

       ミューテックスロックはスレッドクリティカルなリソースアクセスの問題を解決しますが、スレッドの実行順序の問題は解決されていないため、セマフォの概念が導入され、スレッドの実行順序はセマフォによって制御されます。

1.セマフォの初期化

この関数はセマフォを初期化できます。最初のパラメーターはsem_tタイプのアドレスで渡され、2番目のパラメーターはスレッド制御を表すために0で渡されます。それ以外の場合はプロセス制御であり、3番目のパラメーターはセマフォの初期値を表します。0ブロッキングを表します。1は実行を意味します。セマフォが初期化された後、実行が成功すると0を返します。

#include <semaphore.h>
int sem_init(sem_t *sem,int pshared,unsigned int value);
成功:返回0

2.セマフォP / V操作(ブロッキング)

          sem_waitの機能は、指定されたセマフォに使用可能なリソースがあるかどうかを検出することです。使用可能なリソースがない場合は、待機をブロックします。リソースが使用可能な場合は、「sem-1」操作を自動的に実行します。いわゆる「sem-1」は、上記の初期化関数の3番目のパラメーターの値と一致しており、正常に実行されると0を返します。

          sem_post関数は、指定されたセマフォのリソースを解放し、「sem +1」操作を実行します。

上記の2つの機能により、いわゆるPV動作、つまりセマフォの適用と解除が完了し、スレッド実行シーケンスの制御が完了します。

#include <pthread.h>
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
成功:返回0

 

ノンブロッキングセマフォアプリケーションリソース関数:

#include <pthread.h>
int sem_trywait(sem_t *sem);
成功:返回0

3.セマフォの破壊

#include <pthread.h>
int sem_destory(sem_t *sem);
成功:返回0

4.例

#define _GNU_SOURCE 
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <semaphore.h>

sem_t sem1,sem2,sem3;//申请的三个信号量变量

void *fun1(void *arg)
{
	sem_wait(&sem1);//因sem1本身有资源,所以不被阻塞 获取后sem1-1 下次会会阻塞
	printf("%s:Pthread Come!\n",__FUNCTION__);
	sem_post(&sem2);// 使得sem2获取到资源
	pthread_exit(NULL);
}

void *fun2(void *arg)
{
	sem_wait(&sem2);//因sem2在初始化时无资源会被阻塞,直至14行代码执行 不被阻塞 sem2-1 下次会阻塞
	printf("%s:Pthread Come!\n",__FUNCTION__);
	sem_post(&sem3);// 使得sem3获取到资源
	pthread_exit(NULL);
}

void *fun3(void *arg)
{
	sem_wait(&sem3);//因sem3在初始化时无资源会被阻塞,直至22行代码执行 不被阻塞 sem3-1 下次会阻塞
	printf("%s:Pthread Come!\n",__FUNCTION__);
	sem_post(&sem1);// 使得sem1获取到资源
	pthread_exit(NULL);
}

int main()
{
	int ret;
	pthread_t tid1,tid2,tid3;
	ret = sem_init(&sem1,0,1);  //初始化信号量1 并且赋予其资源
	if(ret < 0){
		perror("sem_init");
		return -1;
	}
	ret = sem_init(&sem2,0,0); //初始化信号量2 让其阻塞
	if(ret < 0){
		perror("sem_init");
		return -1;
	}
	ret = sem_init(&sem3,0,0); //初始化信号3 让其阻塞
	if(ret < 0){
		perror("sem_init");
		return -1;
	}
	ret = pthread_create(&tid1,NULL,fun1,NULL);//创建线程1
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	ret = pthread_create(&tid2,NULL,fun2,NULL);//创建线程2
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	ret = pthread_create(&tid3,NULL,fun3,NULL);//创建线程3
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	/*回收线程资源*/
	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);
	pthread_join(tid3,NULL);

	/*销毁信号量*/
	sem_destroy(&sem1);
	sem_destroy(&sem2);
	sem_destroy(&sem3);

	return 0;
}

5:スレッド属性

pthread_create()関数の2番目のパラメーター(pthread_attr_t * attr)は、スレッドの属性を表します。前の例では、値はNULLに設定されています。つまり、デフォルトの属性が使用されており、スレッドの複数の属性を変更できます。これらの属性には、主にバインディング属性、分離属性、スタックアドレス、スタックサイズ、および優先度が含まれます。システムのデフォルトの属性は、バインドなし、分離なし、デフォルトの1Mスタック、および親プロセスと同じ優先度です。以下では、最初に、属性のバインドと属性の分離の基本的な概念について説明します。

  • バインディング属性。

前述のように、Linuxは「1対1」のスレッドメカニズムを使用します。つまり、1つのユーザースレッドが1つのカーネルスレッドに対応します。バインディング属性は、CPUタイムスライスのスケジューリングがカーネルスレッド(つまり、軽量プロセス)に向けられているため、ユーザースレッドがカーネルスレッドに固定的に割り当てられることを意味します。したがって、バインディング属性を持つスレッドは、合計を保証できます。対応するカーネルスレッドがあります。それに。対応する非バインド属性は、ユーザースレッドとカーネルスレッドの関係が常に固定されているわけではなく、システムによって制御されていることを意味します。

  • バインディング属性。

前述のように、Linuxは「1対1」のスレッドメカニズムを使用します。つまり、1つのユーザースレッドが1つのカーネルスレッドに対応します。バインディング属性は、CPUタイムスライスのスケジューリングがカーネルスレッド(つまり、軽量プロセス)に向けられているため、ユーザースレッドがカーネルスレッドに固定的に割り当てられることを意味します。したがって、バインディング属性を持つスレッドは、合計を保証できます。対応するカーネルスレッドがあります。それに。対応する非バインド属性は、ユーザースレッドとカーネルスレッドの関係が常に固定されているわけではなく、システムによって制御されていることを意味します。

これらの属性の設定は、特定の関数を介して行われます。通常、最初にpthread_attr_init()関数を呼び出して初期化し、次に対応する属性設定関数を呼び出し、最後にpthread_attr_destroy()関数を呼び出して、割り当てられた属性構造をクリーンアップして再利用します。ポインタ。バインディング属性を設定する関数はpthread_attr_setscope()、スレッド分離属性を設定する関数はpthread_attr_setdetachstate()、スレッド優先度を設定する関連関数はpthread_attr_getschedparam()(スレッド優先度を取得)およびpthread_attr_setschedparam()(スレッドを設定)です。優先)。これらのプロパティを設定した後、pthread_create()関数を呼び出してスレッドを作成できます。

#include <pthread.h>

int pthread_attr_init(pthread_attr_t *attr)
attr:线程属性结构指针
返回值: 成功0、 错误返回错误码


int pthread_attr_setscope(pthread_attr_t *attr, int scope)
attr:线程属性结构指针
scope:{
    PTHREAD_SCOPE_SYSTEM:绑定
    PTHREAD_SCOPE_PROCESS:非绑定 
}
返回值:成功0、错误-1


int pthread_attr_setscope(pthread_attr_t *attr, int detachstate)
attr:线程属性
detachstate:{
    PTHREAD_CREATE_DETACHED:分离
    PTHREAD _CREATE_JOINABLE:非分离
}
返回值:成功0、错误返回错误码

int pthread_attr_getschedparam (pthread_attr_t *attr, struct sched_param *param)
attr:线程属性结构指针
param:线程优先级
返回值:成功:0、错误返回错误码

int pthread_attr_setschedparam (pthread_attr_t *attr, struct sched_param *param)
attr:线程属性结构指针
param:线程优先级
返回值:成功0、错误返回错误码

例:不必要な複雑さを回避するために、ここにスレッドが作成されます。このスレッドにはバインディングプロパティとデタッチメントプロパティがあり、メインスレッド
はpthread_join()関数を呼び出す代わりに、finish_flagフラグ変数を介しスレッドの終わりを取得します。

/*thread_attr.c*/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define REPEAT_NUMBER 3 /* 线程中的小任务数 */
#define DELAY_TIME_LEVELS 10.0 /* 小任务之间的最大时间间隔 */
int finish_flag = 0;

void *thrd_func(void *arg)
{
    int delay_time = 0;
    int count = 0;
    printf("Thread is starting\n");
    for (count = 0; count < REPEAT_NUMBER; count++)
    {
        delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1;
        sleep(delay_time);
        printf("\tThread : job %d delay = %d\n", count, delay_time);
    }
    printf("Thread finished\n");
    finish_flag = 1;
    pthread_exit(NULL);
}

int main(void)
{
    pthread_t thread;
    pthread_attr_t attr;
    int no = 0, res;
    void * thrd_ret;
    srand(time(NULL));
    /* 初始化线程属性对象 */
    res = pthread_attr_init(&attr);
    if (res != 0)
    {
        printf("Create attribute failed\n");
        exit(res);
    }
    /* 设置线程绑定属性 */
    res = pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
    /* 设置线程分离属性 */
    res += pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    if (res != 0)
    {
        printf("Setting attribute failed\n");
        exit(res);
    }
    res = pthread_create(&thread, &attr, thrd_func, NULL);
    if (res != 0)
    {
        printf("Create thread failed\n");
        exit(res);
    }
    /* 释放线程属性对象 */
    pthread_attr_destroy(&attr);
    printf("Create tread success\n");
    while(!finish_flag)
    {
        printf("Waiting for thread to finish...\n");
        sleep(2);
    }
    return 0;
}

 

おすすめ

転載: blog.csdn.net/qq_34968572/article/details/107490102