Linux线程编程初步

一些历史背景

Linux 间接起源于 Unix,而 Linux 诞生时并不存在 "线程" 的概念

在 20 世纪 90 年代线程才流行起来,POSIX Thread 标准于 1995 年确立

Unix 中引入 Thread 之后,大量函数被重写,信号机制也变得复杂

2005 年之后,处理器生成厂商向超线程和多核架构靠拢

一些常见的概念

物理处理器:安装在主机上的真实的处理器硬件

逻辑处理器:逻辑处理器与超线程技术相关

  • 不支持超线程:逻辑处理器的数量等于核心数的数量
  • 支持超线程:逻辑处理器的数量是处理器核心数的两倍

核心数:即多核处理器中的内核数量

  • 通过工艺手段将多个完整的 CPU 塞进一个处理器封装中 (每个 CPU 就是一个核)

线程与进程的关系

进程:应用程序的一次加载执行 (系统进行资源分配的基本单位)

线程:进程中的程序执行流

  • 一个进程中可以存在多个线程 (至少存在一个线程)
  • 每个线程执行不同的任务 (多个线程可并行执行)
  • 同一个进程中的多个线程共享进程的系统资源

进程中的多个线程并行执行,共享进程资源!

初探线程编程模型

多线程 vs 多进程

创建 / 销毁 线程花费的时间 < 创建 / 销毁 进程花费的时间

多线程切换开销 < 多进程切换开销

线程间数据共享复杂度 < 进程间数据共享复杂度

多线程代码稳定性 < 多进程代码稳定性

多线程代码复杂度 > 多进程代码复杂度

Linux 多线程 API 函数

头文件:#include<pthread.h>

线程创建函数:int pthread_create(pthread_t* thread, const pthread_attr_t* attr, void* (*start_routine)(void*), void* arg);

  • thread:pthread_t 变量的地址,用于返回线程标识
  • attr:线程的属性,可设置为 NULL,即:使用默认属性
  • start_routine:线程入口函数
  • arg:线程入口函数参数

线程标识:

  • pthread_t pthread_self(void);
  • 获取当前线程的 ID 标识

线程等待:

  • int pthread_join(pthread_t thread, void** retval);
  • 等待目标线程执行结束

多线程编程示例

实验一:性能对比

相同功能的 多线程程序 vs 多进程程序

对比项:创建 / 销毁

实验二:数据共享

多线程程序共享一段内存 => "全局变量"

多进程程序共享一段内存 => "机制复杂"

多线程 vs 多进程 (性能对比)

test.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <math.h>
#include <errno.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <pthread.h>

#define NSECS_PER_MSEC 1000000UL
#define NSECS_PER_SEC 1000000000UL
#define TEST_LOOPS  10000
#define DiffNS(begin, end) ((end.tv_sec - begin.tv_sec) * NSECS_PER_SEC \
                            + (end.tv_nsec - begin.tv_nsec))

void* thread_entry(void* arg)
{
    return NULL;
}

void thread_test()
{
    printf("thread test: \n");
    
    struct timespec begin = {0};
    struct timespec end = {0};
    pthread_t tid = 0;
    int i = 0;
    int diff = 0;
    
    clock_gettime(CLOCK_MONOTONIC, &begin);
    
    for(i=0; i<TEST_LOOPS; i++)
    {
        pthread_create(&tid, NULL, thread_entry, NULL);
        pthread_join(tid, NULL);
    }
    
    clock_gettime(CLOCK_MONOTONIC, &end);
    
    diff = DiffNS(begin, end) / NSECS_PER_MSEC;
    
    printf("result = %dms\n", diff);
}

void process_test()
{
    printf("process test: \n");
    
    struct timespec begin = {0};
    struct timespec end = {0};
    pid_t pid = 0;
    int i = 0;
    int diff = 0;
    
    clock_gettime(CLOCK_MONOTONIC, &begin);
    
    for(i=0; i<TEST_LOOPS; i++)
    {
        pid = fork();
        
        if( pid ) waitpid(pid, NULL, 0);
        else return;
    }
    
    clock_gettime(CLOCK_MONOTONIC, &end);
    
    diff = DiffNS(begin, end) / NSECS_PER_MSEC;
    
    printf("result = %dms\n", diff);
}

int main()
{
    thread_test();
    process_test();
    
    return 0;
}

thread_test() 函数用于测试创建和销毁 TEST_LOOPS 次线程所花费的时间

process_test() 函数用于测试创建和销毁 TEST_LOOPS 次进程所花费的时间

程序运行结果如下图所示:

进程创建和销毁比线程创建更加耗时

多线程内存共享

shm-thread.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <math.h>
#include <errno.h>
#include <sched.h>
#include <fcntl.h>
#include <pthread.h>

void* thread_entry(void* arg)
{
    printf("pid = %d, ppid = %d, pgid = %d\n", 
            getpid(), getppid(), getpgrp());
            
    char* shmaddr = arg;
    
    strcpy(shmaddr, "D.T.Software");
    
    return NULL;
}

int main()
{
    char* mem = malloc(128);
    pthread_t child = 0;
    
    printf("mem = %p\n", mem);
    
    if( mem == NULL )
    {
        printf("malloc error\n");
        exit(1);
    } 
    
    int r = pthread_create(&child, NULL, thread_entry, mem);
    
    if( r == 0 )
    {
        printf("pid = %d, ppid = %d, pgid = %d\n", 
            getpid(), getppid(), getpgrp());
        
        char* shmaddr = mem;
        
        pthread_join(child, NULL);
        
        printf("%s\n", shmaddr);
        
    }
    else
    {
        printf("create thread error...\n");
    }
    
    free(mem);
    
    return 0;
}

同一个进程下的不同线程共享进程的系统资源,子线程可以访问到主线程中申请的堆空间内存

程序运行结果如下图所示:

多进程内存共享

shm-proc.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <math.h>
#include <errno.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <fcntl.h>

#define PATH_NAME "."
#define PROJ_ID   88

int main()
{
    key_t k = ftok(PATH_NAME, PROJ_ID);  
    int shmid = shmget(k, 128, IPC_CREAT | S_IRWXU);
    
    printf("shmid = %d\n", shmid);
    
    if( shmid == -1 )
    {
        printf("shmget error\n");
        exit(1);
    } 
    
    int pid = fork();
    
    if( pid )
    {
        printf("pid = %d, ppid = %d, pgid = %d\n", 
            getpid(), getppid(), getpgrp());
        
        char* shmaddr = shmat(shmid, NULL, 0);
        
        waitpid(pid, NULL, 0);
        
        printf("%s\n", shmaddr);
        
    }
    else if( pid == 0 )
    {
        printf("pid = %d, ppid = %d, pgid = %d\n", 
            getpid(), getppid(), getpgrp());
            
        char* shmaddr = shmat(shmid, NULL, 0);
        
        strcpy(shmaddr, "D.T.Software");
            
        exit(0);
    }
    else
    {
        printf("fork error...\n");
    }
    
    shmctl(shmid, IPC_RMID, NULL); 
    
    return 0;
}

fork() 会复制父进程的地址空间,父进程和子进程位于不同的地址空间,这里是通过共享内存的方式来实现内存共享的

程序运行结果如下图所示:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值