《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() 函数;

相关推荐
hgdlip2 小时前
使用代理ip和本地网络的区别是什么
网络·网络协议·tcp/ip
Estar.Lee10 小时前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
傻啦嘿哟11 小时前
代理IP在后端开发中的应用与后端工程师的角色
网络·网络协议·tcp/ip
Estar.Lee14 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
友友马14 小时前
『 Linux 』网络层 - IP协议(一)
linux·网络·tcp/ip
hgdlip18 小时前
主IP地址与从IP地址:深入解析与应用探讨
网络·网络协议·tcp/ip
今天我刷leetcode了吗19 小时前
docker 配置同宿主机共同网段的IP 同时通过通网段的另一个电脑实现远程连接docker
tcp/ip·docker·电脑
爱分享的码瑞哥1 天前
Python爬虫中的IP封禁问题及其解决方案
爬虫·python·tcp/ip
_不会dp不改名_1 天前
HCIA笔记3--TCP-UDP-交换机工作原理
笔记·tcp/ip·udp
co0t1 天前
计算机网络(14)ip地址超详解
服务器·tcp/ip·计算机网络