『TCP/IP ネットワークプログラミング』読書メモ -- マルチスレッドサーバー側の実装

1 -- マルチスレッドの利点

マルチプロセス サーバーの欠点:

        ① プロセスを作成するプロセスには一定のオーバーヘッドがかかります。

        ② プロセス間のデータ交換を完了するには、特別な IPC 技術が必要です。

        ③ プロセス間のコンテキスト切り替えは、プロセス作成時の最大のオーバーヘッドです。

マルチスレッドの利点:

        ① スレッドの作成とコンテキストの切り替えは、プロセスの作成とコンテキストの切り替えよりも高速です。

        ② スレッド間でデータをやり取りする際に特別な技術は必要ありません。

2--プロセスとスレッドの違い

        各プロセスには独立したメモリ空間と独自のデータ領域、ヒープ領域、スタック領域があります。

        各スレッドは独自のスタック領域のみを持ち、データ領域とヒープ領域はスレッド間で共有されるため、スレッド間のコンテキスト切り替え時にデータ領域とヒープ領域を切り替える必要がなく、データ領域とヒープ領域を利用できます。データを交換するため。

        

        プロセス: オペレーティング システム内で別個の実行フローを構成する単位。

        スレッド: プロセス内で別個の実行フローを形成する単位。

        プロセスはオペレーティング システム内で複数の実行ストリームを生成し、スレッドは同じプロセス内で複数の実行ストリームを作成します。

3 -- スレッドの作成

#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 的变量地址值
// attr 表示用于传递线程属性的参数,传递 NULL 时表示创建默认属性的线程
// start_routine 表示线程的入口函数
// arg 表示传递给线程入口函数的参数
// gcc thread1.c -o thread1 -lpthread
// ./thread1

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* thread_main(void* arg){ // 线程入口函数
    int i;
    int cnt = *((int*)arg);
    for(i = 0; i < cnt; i++){
        sleep(1);
        puts("running thread");
    }
    return NULL;
}

int main(int argc, char* argv[]){
    pthread_t t_id;
    int thread_param = 5;
    if(pthread_create(&t_id, NULL, thread_main, (void*)&thread_param) != 0){ // 创建线程
        puts("pthread_create() error");
        return -1;
    }
    sleep(10);
    puts("end of main");
    return 0;
}

4 -- スレッドの使用法

        pthread_join(ID) を呼び出すと、その ID に対応するスレッドが終了するまで、プロセスまたはスレッドが待機状態になることがあります。

#include <pthread.h>
int pthread_join(pthread_t thread, void** status);
// 成功时返回0,失败时返回其他值
// thread 表示线程ID,只有该线程终止后才会从函数返回
// status 表示保存线程返回值的指针变量地址值 
// gcc thread2.c -o thread2 -lpthread
// ./thread2

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

void* thread_main(void *arg){
    int i;
    int cnt = *((int*)arg);
    char* msg = (char*)malloc(sizeof(char)*50);
    strcpy(msg, "Hello, I'm thread~ \n");

    for(i = 0; i < cnt; i++){
        sleep(1);
        puts("running thread");
    }
    return (void*)msg;
}

int main(int argc, char* argv[]){
    pthread_t t_id;
    int thread_param = 5;
    void* thr_ret;

    // 创建线程
    if(pthread_create(&t_id, NULL, thread_main, (void*)&thread_param) != 0){
        puts("pthread_create() error");
        return -1;
    }
    // 阻塞,等待线程返回
    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;
}

5 -- スレッドの安全性の問題

        複数のスレッドが関数を呼び出してクリティカル セクション コードを同時に実行すると、問題が発生する可能性があります。

        クリティカル セクションが問題を引き起こすかどうかに応じて、関数はスレッド セーフな関数と非スレッド セーフな関数に分類できます。

        スレッド セーフな関数は、複数のスレッドによって同時に呼び出されても問題は発生しませんが、非スレッド セーフな関数が同時に呼び出される場合には問題が発生します。

        次のコードは、複数のスレッドがクリティカル セクション コードにアクセスしてグローバル変数を同時に操作すると、予期しない問題が発生する可能性があることを示しています。

// gcc thread4.c -D_REENTRANT -o thread4 -lpthread
// ./thread4

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#define NUM_THREAD 100

long long num = 0;

void* thread_inc(void* arg){
    int i;
    for(i = 0; i < 50000000; i++){
        num += 1;
    }
    return 0;
}

void* thread_des(void* arg){
    int i;
    for(i = 0; i < 50000000; i++){
        num -= 1;
    }
    return 0;
}

int main(int argc, char* argv[]){
    pthread_t thread_id[NUM_THREAD];
    int i;

    printf("sizeof long long: %ld \n", sizeof(long long));
    for(i = 0; i < NUM_THREAD; i++){
        // 各创建50个线程,分别执行对全局变量 num 的加减操作
        if(i%2){
            pthread_create(&(thread_id[i]), NULL, thread_inc, NULL);
        }
        else{
            pthread_create(&(thread_id[i]), NULL, thread_des, NULL);
        }
    }

    for(i = 0; i < NUM_THREAD; i++){
        pthread_join(thread_id[i], NULL);
    }

    printf("result: %lld \n", num);
    return 0;
}

        通常の結果は 0 であるはずですが、実際の結果はそうではなく、複数のスレッドがクリティカル セクションに同時にアクセスすると、データの競合などの問題が発生します。

        スレッドが変数 num にアクセスするとき、1 つのスレッドが操作を完了するまで、他のスレッドがその変数にアクセスできないようにする必要があります。これが同期です。スレッドの同期は、スレッドのアクセス順序によって引き起こされる問題を解決するために使用されます。

6--ミューテックス

        ミューテックスは、複数のスレッドが同時にアクセスできないことを意味し、主にスレッド同期アクセスの問題を解決するために使用されます。

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* attr);
int pthread_mutex_destroy(pthread_mutex_t* mutex);
// 成功时返回 0,失败时返回其他值
// mutex 表示创建和销毁互斥量时传递保存和销毁互斥量的变量地址值
// attr 传递即将创建的互斥量属性,没有特别需要指定的属性时传递 NULL

int pthread_mutex_lock(pthread_mutex_t* mutex); // 上锁
int pthread_mutex_unlock(pthread_mutex_t* mutex); // 解锁
// 成功时返回 0,失败时返回其他值

pthread_mutex_t mutex;
pthread_mutex_lock(&mutex);
// 临界区开始
// ...
// 临界区结束
pthread_mutex_unlock(&mutex);

7--信号量

        バイナリ セマフォを使用して、制御スレッド プログラムを中心とした同期方法を完了します。

#include <semaphore.h>
int sem_init(sem_t* sem, int pshared, unsigned int value);
int sem_destroy(sem_t* sem);
// 成功时返回 0,失败时返回其他值
// sem 表示信号量的变量地址值
// pshared 传递其他值时,创建可由多个进程共享的信号量;传递 0 时,创建只允许一个进程内部使用的信号量
// value 表示指定新创建的信号量的初始值

int sem_post(sem_t* sem); // 信号量增加1
int sem_wait(sem_t* sem); // 信号量减少1
// 成功时返回 0,失败时返回其他值

sem_wait(&sem); // 信号量变为0
// 临界区的开始
// ...
// 临界区的结束
sem_post(&sem); // 信号量变为1

8--スレッドの破壊

スレッドを破棄する 3 つの方法:

        ① main 関数の return ステートメントを呼び出します。

        ② pthread_join() 関数を呼び出します。

        ③ pthread_detach() 関数を呼び出します。

おすすめ

転載: blog.csdn.net/weixin_43863869/article/details/132857216