《TCP/IP网络编程》阅读笔记--多线程服务器端的实现

1--多线程的优点

多进程服务器的缺点:

① 创建进程的过程会带来一定的开销;

② 为了完成进程间的数据交换,需要特殊的 IPC 技术;

③ 进程间的上下文切换是创建进程时的最大开销;

多线程的优点:

① 线程的创建和上下文切换比进程的创建和上下文切换更快;

② 线程间交换数据时无需特殊技术;

2--进程和线程的差异

每个进程拥有独立的内存空间,拥有自己的数据区、堆区域和栈区域;

每个线程只拥有自己的栈区域,线程间共享数据区和堆区域;因此线程间上下文切换时不需要切换数据区和堆,可以利用数据区和堆区域交换数据;

进程:在操作系统构成单独执行流的单位;

线程:在进程构成单独执行流的单位;

进程是在操作系统内部生成多个执行流,线程就是在同一进程内部创建多条执行流;

3--线程创建

cpp 复制代码
#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 表示传递给线程入口函数的参数
cpp 复制代码
// 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 对应的线程终止为止;

cpp 复制代码
#include <pthread.h>
int pthread_join(pthread_t thread, void** status);
// 成功时返回0,失败时返回其他值
// thread 表示线程ID,只有该线程终止后才会从函数返回
// status 表示保存线程返回值的指针变量地址值 
cpp 复制代码
// 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--线程安全问题

多个线程同时调用函数执行临界区代码时,会出现问题;

根据临界区是否引起问题,函数可分为:线程安全函数和非线程安全函数;

线程安全函数被多个线程同时调用时不会引发问题,非线程安全函数被同时调用时会引发问题;
以下代码展示了多个线程同时访问临界区代码操作全局变量时会出现意向不到的问题;

cpp 复制代码
// 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 时应阻止其他线程访问,直到一个线程完成运算,这就是同步(Synchronization);线程同步用于解决线程访问顺序引发的问题;

6--互斥量

互斥量表示不允许多个线程同时访问,主要用于解决线程同步访问的问题;

cpp 复制代码
#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--信号量

利用二进制信号量完成控制线程程序为中心的同步方法;

cpp 复制代码
#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 函数的返回语句;

② 调用 pthread_join() 函数;

③ 调用 pthread_detach() 函数;

相关推荐
hzyyyyyyyu6 小时前
隧道技术-tcp封装icmp出网
网络·网络协议·tcp/ip
进击的程序汪16 小时前
深入理解网络监听:TCP、UDP、IPv4 和 IPv6
网络·tcp/ip·udp
勤奋的凯尔森同学19 小时前
ubuntu22.04上手指南(更新阿里源、安装ssh、安装chrome、设置固定IP、安装搜狗输入法)
chrome·tcp/ip·ssh·ubuntu22.04·搜狗输入法·ubuntu24.04
hgdlip20 小时前
家里电脑ip地址怎么设置?详细指导
网络·tcp/ip·智能路由器·家里电脑
米饭是菜qy21 小时前
TCP 三次握手意义及为什么是三次握手
服务器·网络·tcp/ip
yaoxin52112321 小时前
第十九章 TCP 客户端 服务器通信 - 数据包模式
服务器·网络·tcp/ip
IPdodo全球网络1 天前
解析“ChatGPT网络错误”:从网络专线到IP地址的根源与解决方案
网络·tcp/ip·chatgpt
hgdlip2 天前
本地连接IP地址的自主设置指南‌
网络·网络协议·tcp/ip·本地连接
დ旧言~2 天前
【网络】子网掩码
服务器·网络·网络协议·tcp/ip·php·apache
前端李易安2 天前
什么是UDP和TCP?有什么区别?应用场景分别都有哪些?
网络·tcp/ip·udp