この記事は合計 2820 文字で構成されており、推定読了時間は 6 分です。
目次
プロセスの欠陥
各プロセスには独立したメモリ空間があるため、プロセスの作成とコピーはオペレーティング システムに大きな負担をもたらします。プロセスの作成とプロセス間データ交換に加えて、これらのオーバーヘッドは「コンテキスト スイッチ」からも発生し、「コンテキスト スイッチ」が最も多くのリソースを消費します。
コンテキストスイッチング、その実際の意味はタスクスイッチング、または CPU レジスタスイッチングです。マルチタスク カーネルは、別のタスクを実行することを決定すると、実行中のタスクの現在の状態、つまり CPU レジスタの内容全体を保存します。これらの内容はタスク自身のスタックに保存され、スタックへのプッシュが完了すると、次に実行するタスクの現在のステータスがタスクのスタックからCPUレジスタに再ロードされ、次のタスクの実行が開始されます。 「コンテキストスイッチング」。
プロセスの利点を維持し、プロセスによってもたらされるスペースとデータ転送の時間消費を改善するために、人々は「スレッド」を設計しました。
スレッドを作成すると、プロセスによるさまざまな影響が軽減され、スレッドにはプロセスと比較して次のような利点があります。
- スレッドの作成とコンテキストの切り替えは、プロセスの作成とコンテキストの切り替えよりも高速です。
- スレッド間でデータを交換するときに追加のテクノロジーを導入する必要はありません。
スレッドとプロセスの違い
各プロセスのメモリ空間は、グローバル変数を格納するデータ領域、ヒープ(動的割り当て関数mallocなどで提供される領域)、スタック(関数の実行時に生成される)で構成されます。各プロセスにはこの独立した空間があり、そのモデルは次の図に示されています。
スレッドはプロセス設計モデルに基づき、複数のコードの実行フローをデータ領域とヒープに集中させ、スタック領域を分離し、以下のようなモデル構造を設計しています。
複数のスレッドがデータ領域とヒープを共有できるようになります。
したがって、プロセスとスレッドの定義は次のように大まかに要約できます。
- プロセス: オペレーティング システムにおけるリソース割り当ての最小単位およびスレッドのコンテナー
- スレッド: プロセス内で個別の実行フローを構成する単位であり、CPU スケジューリングの基本単位です。
スレッドの作成と実行のプロセス
pthread_create
スレッドには個別の実行フローがあるため、専用の main 関数を設計する必要があり、実行フロー内の関数の実行をオペレーティング システムに要求する必要があります。
スレッドを作成するには、次のヘッダー ファイルと関数を参照できます。
#include <pthread.h>
int pthread_create (pthread_t * restrict thread , const pthread_attr_t * restrict attr , void * (* start_routine)(void * ), void * restrict arg);
// 成功时返回 0, 失败时返回其他值。
/* 参数含义
thread: 保存新创建线程 ID 的变量地址值。线程与进程相同,也需要用于区分不同线程的ID。
attr: 用于传递线程属性的参数,传递 NULL时,创建默认属性的线程。
start routïne: 相当于线程 main 函数的、在单独执行流中执行的函数地址值(函数指针) 。
arg: 通过第三个参数传递调用函数时包含传递参数信息的变量地址值。
*/
例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *thread_func1(void *arg);
int main(int argc, char *argv[])
{
pthread_t t_id; // 线程的id,类比与进程号
int thread_cycle = 10;
if (pthread_create(&t_id, NULL, thread_func1, (void *)&thread_cycle /*注意要强转*/) != 0)
{
puts("pthread creation error");
return -1;
};
sleep(11); //让Main函数停留 11 s,一般而言,等待事件设置的都比理论上的多1-2s,以保证能够执行完整
puts("thread execution end");
return 0;
}
void *thread_func1(void *arg)
{
int i;
int cnt = *((int *)arg); // 将指向void类型的指针类型转换为指向int类型的指针,然后再对其进行取值
for (i = 0; i < cnt; i++)
{
sleep(1);
printf("cycle: %d \n", i);
}
return NULL;
}
操作結果:
上記のコードの main 関数のスリープをスレッドの理論上の合計実行時間よりも低い値に変更すると、実行フローが不完全になります。
ただし、睡眠時間は客観的な時間、つまりリアルタイムに基づいて推定される時間です。コンピュータがスレッド内で関数を実行する場合、必ずしもこの時間に従うとは限りません。スレッド実行フローのタイム スライスをより正確に制御する方法はあるのでしょうか?
pthread_join
pthread_join関数 を使用してみましょう。その構造と参照ヘッダー ファイルは次のとおりです。
# include <pthread.h>
int pthread_join(pthread_t thread , void ** status);
//成功时返回 0 ,失败时返回其他值。
/* 参数含义
thread:欲操作的线程,以该线程的id传入。
status:保存该线程 main 函数所返回的指针变量地址值 。
*/
例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
void* thread_func2(void *arg);
int main(int argc, char *argv[])
{
pthread_t t_id;
int thread_cycle= 5;
void * thr_ret;
if(pthread_create(&t_id, NULL, thread_func2, (void*)&thread_cycle)!=0)
{
puts("pthread creation error");
return -1;
};
//等待线程t_id的执行完毕。完毕后该线程返回的数据将存放在thr_ret中
if(pthread_join(t_id, &thr_ret)!=0)
{
puts("pthread_join error");
return -1;
};
//将线程执行完后的返回值输出。执行这条指令时线程已结束。
printf("Thread return message: %s \n", (char*)thr_ret);
free(thr_ret);
return 0;
}
void* thread_func2(void *arg)
{
int i;
int cnt=*((int*)arg);
char * msg=(char *)malloc(sizeof(char)*20);
strcpy(msg, "Hello, I’m thread but now I am dead and buried. \n");
for(i=0; i<cnt; i++)
{
sleep(1);
printf("cycle: %d \n",i);
}
return (void*)msg; //这是
}
操作結果:
ループが終了すると、つまり理論上は 5 秒後、スレッドは終了し、値を返します。このとき、変数はpthread_join関数の後に正しく出力されます。明らかに、この方法は手動による睡眠推定よりも正確に時間を制御できます。