一些历史背景
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() 会复制父进程的地址空间,父进程和子进程位于不同的地址空间,这里是通过共享内存的方式来实现内存共享的
程序运行结果如下图所示:

916

被折叠的 条评论
为什么被折叠?



