目录
一、线程
(一)线程概念
(二)特征
(三)优缺点
二、线程与进程的区别(面问)
三、多线程程序设计步骤
四、线程的创建(相关函数)
1、pthread_create()
2、pthread_self()
3、扩展:strerror()
五、线程的退出
1、pthread_exit()
2、pthread_cancel()
六、线程的回收
(一)线程的回收机制
(二)线程回收相关函数 pthread_join()
(三)次线程的回收策略
七、线程的参数,返回值
八、分离属性
1、pthread_attr_init()
2、pthread_attr_destroy()
3、pthread_attr_setdetachstate()
4、pthread_deatch()
线程清理函数
5、pthread_cleanup_push()
6、pthread_cleanup_pop()
7、注:
8、示例
一、线程
(一)线程概念
1、线程是轻量级进程,一般是一个进程中的多个任务(一个进程可以有多个线程);
2、进程是系统中最小的资源分配单位;
线程是系统中最小的执行单位。
(二)特征
1、共享资源:
2、效率高 30%
3、三方库: pthread clone posix
4.、编写代码头文件: pthread.h
5、 编译代码加载库: gcc x.c -lpthread
扩:以lib开头 .so结尾的为动态库(在内核中),中间的pthread为库名。
(三)优缺点
优点:比多进程节省资源,可以共享变量。
缺点:1.线程和进程相比,稳定性,稍微差些
2.线程的调试gdb,相对麻烦些,因为并发
二、线程与进程的区别(面问)
1、资源:
(1)线程比进程多了共享资源;
(2)线程又具有部分私有资源;
(3)进程间只有私有资源没有共享资源。
2、空间:
(1)进程空间独立(写时复制),不能直接通信;
(2)线程可以共享空间(栈区默认不共享),可以直接通信。
3、不同点:
(1)创建开销不一样,进程创建需要3G空间,线程创建只需要8M,线程并发度高于进
程;
(2)进程变量不共享;
(3)进程切换(复杂)需要的缓存空间多,线程切换栈区,PC指针也发生变化;
(4)进程可申请到硬件资源;
4、稳定性差异:
(1)线程稳定性差(其中一个线程崩溃或严重异常,进程直接结束);
(2)项目任务复杂,用进程做;简单小任务,用线程做;
5、共同点:都能并发。
6、考虑用线程还是进程的两个准则:
(1)稳定性;
(2)看资源够不够用(够用用线程)。
三、多线程程序设计步骤
1、创建多线程(只要创建成功就启动了)
2、线程空间操作 (设计函数表达要干什么)
3、线程资源回收(栈区回收,默认进程结束栈区不释放)
注:
(1)进程中的第一个线程为主线程,主线程有且仅有一个,(主线程号和进程号一
致)其它次线程为平级关系;
(2)主线程不需创建,运行a.out就出来了,后调用函数创建其他空间,在进程空间新
开栈区(8M)。
四、线程的创建(相关函数)
1、pthread_create()
(1)函数原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
(2)功能:该函数可以创建指定的一个线程。
(3)参数:
thread 线程id,需要实现定义并由该函数返回。
attr 线程属性,一般是NULL,表示默认属性。
start_routine 指向指针函数的函数指针, 本质上是一个函数的名称即可。称为th 回调
函数,是线程的执行空间。
(4)返回值:成功 0;失败 错误码
(5)注意:
一次pthread_create执行只能创建一个线程。
每个进程至少有一个线程称为主线程。
主线程退出则所有创建的子线程都退出。
主线程必须有子线程同时运行才算多线程程序。
线程id是线程的唯一标识,是CPU维护的一组数字。
pstree 查看系统中多线程的对应关系。
多个子线程可以执行同一回调函数。
(6)示例
注:gcc编译命令:gcc 01pthread_create.c -lpthread
2、pthread_self()
(1)函数原型:pthread_t pthread_self(void);
(2)功能:获取当前线程的线程id(谁调就返回谁的id号)
(3)参数:无
(4) 返回值:成功 返回当前线程的线程id; 失败 -1
(5)用户层表示(ps命令)与内核层表示(数字大)不同,使用内核层表示
用户层查看(man ps ps-eLf)
(6)示例:
3、扩展:strerror()
(1)报错函数,用于线程报错(多为极端情况使用),也可调用perror()函数(大部分
情况使用)
(2)strerror(errno)中errno为错误码
五、线程的退出
1、pthread_exit()
自行退出 = =自杀 ==子线程自己退出
(1)函数原型:void pthread_exit(void *retval); exit return p;
(2)功能:子线程自行退出
(3)参数: retval 线程退出时候的返回状态,临死遗言。
(4)返回值:无
(5)示例:
2、pthread_cancel()
强制退出 ==他杀 ==主线程结束子线程
(1)函数原型:int pthread_cancel(pthread_t thread);
(2)功能:请求结束一个线程
(3)参数:thread 请求结束一个线程tid
(4)返回值:成功 0;失败 -1
(5)示例:
六、线程的回收
(一)线程的回收机制
1、不同与进程没有孤儿线程和僵尸线程;
2、主线程结束任意生成的子线程都会结束;
3、子线程的结束不会影响主线程的运行。
(二)线程回收相关函数 pthread_join()
1、函数原型:int pthread_join(pthread_t thread, void **retval);
2、功能:通过该函数可以将指定的线程资源回收(线程结束栈区默认没被释放),
该函数具有 阻塞等待功能,如果指定的线程没有结束,则回收线程会阻塞。
3、 参数:thread 要回收的子线程tid
retval 要回收的子线程返回值/状态;ptread_exit(值);
二级指针改变指针的指向
4、返回值:成功 0; 失败 -1
5、扩展:Tcb块(线程控制块)
(进程中有10个线程,pcb块中就有10个tcb块)
6、次线程返回的不能为局部变量(解决办法:加static,malloc申请(+free()))
7、示例:
(1)pthread_join(NULL);
(2)void* ret;
pthread_join(tid,&ret);
8、练习:
(三)次线程的回收策略
1、如果预估子线程可以有限范围内结束则正常用pthread_join等待回收;
2、如果预估子线程可能休眠或者阻塞则等待一定时间后强制回收;
3、如果子线程已知必须长时间运行则,不再回收其资源。
七、线程的参数,返回值
1、传参数整数示例:
传参目的:降低程序耦合性(传参>定义全局变量)
2、结构体示例:
#include <stdio.h> // 包含标准输入输出头文件
#include <stdlib.h> // 包含标准库头文件(含内存分配、atoi等函数)
#include <string.h> // 包含字符串操作头文件(如strcat、strlen等)
#include <unistd.h> // 包含UNIX系统相关头文件(此处可能未实际使用)
#include <pthread.h> // 包含POSIX线程头文件,用于多线程编程
typedef struct // 定义结构体类型PER
{
char name[50]; // 姓名,字符数组长度50
int age; // 年龄,整数类型
char addr[50]; // 地址,字符数组长度50
} PER; // 结构体别名PER
void* th(void* arg) // 线程函数,返回值和参数均为void指针
{
PER per = (PER*)arg; // 将参数强制转换为PER结构体指针
printf("th:%s %d %s\n", per->name, per->age, per->addr); // 打印线程中结构体信息
strcat(per->name, "1"); // 向姓名后拼接字符串"1"(修改原数据)
return per; // 返回结构体指针(作为线程返回值)
}
int main(int argc, char argv) // 主函数,argc为参数个数,argv为参数数组
{
PER per; // 定义PER结构体变量per
bzero(&per, sizeof(per)); // 用bzero函数将per内存块清零(初始化)
printf("pls name:"); // 提示输入姓名
fgets(per.name, sizeof(per.name), stdin); // 读取输入到name数组,含换行符
per.name[strlen(per.name)-1] = '\0'; // 删除末尾换行符(替换为字符串结束符)
char buf[5] = {0}; // 定义缓冲区,用于临时存储年龄输入
printf("pls age:"); // 提示输入年龄
fgets(buf, sizeof(buf), stdin); // 读取输入到buf(最多4个字符+终止符)
per.age = atoi(buf); // 将buf字符串转换为整数赋给age
printf("pls addr:"); // 提示输入地址
fgets(per.addr, sizeof(per.addr), stdin); // 读取输入到addr数组,含换行符
per.addr[strlen(per.addr)-1] = '\0'; // 删除addr末尾换行符
pthread_t tid; // 定义线程ID变量tid
pthread_create(&tid, NULL, th, &per); // 创建线程,传入per地址作为参数
void ret = NULL; // 定义线程返回值存储变量
pthread_join(tid, &ret); // 等待线程结束,获取返回值
// 打印主线程中获取的线程返回值(结构体信息)
printf("join ret :%s %d %s\n", ((PER)ret)->name, ((PER*)ret)->age, ((PER*)ret)->addr);
system("pause"); // 调用系统命令pause(Windows环境暂停程序)
return 0; // 主函数正常退出
}
八、分离属性
目的:线程消亡,自动回收空间。
1、pthread_attr_init()
(1)函数原型:int pthread_attr_init(pthread_attr_t *attr);
(2)功能:初始化一个attr的变量
(3)参数:attr,需要变量来接受初始值
(4)返回值:0 成功,非0 错误
2、pthread_attr_destroy()
(1)函数原型:int pthread_attr_destroy(pthread_attr_t *attr);
(2)功能:销毁attr变量
(3)参数:attr,属性变量
(4)返回值:0 成功,非0 错误
3、pthread_attr_setdetachstate()
(1)函数原型: int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
(2)功能:把一个线程设置成相应的属性
(3)参数:attr,属性变量,有init函数初始化他。
detachstate:有2个可选值,
PTHREAD_CREATE_DETACHED
4、pthread_deatch()
(1)函数原型:int pthread_deatch(pthread_t thread);
(2)功能:设置分离属性
(3)参数:线程id号,填自己的id
线程清理函数
5、pthread_cleanup_push()
(1)函数原型:void pthread_cleanup_push(void (*routine)(void *), void *arg);
(2)功能:注册一个线程清理函数
(3)参数:routine,线程清理函数的入口
arg,清理函数的参数。
(4)返回值:无
6、pthread_cleanup_pop()
(1)函数原型:void pthread_cleanup_pop(int execute);
(2)功能:调用清理函数
(3)参数:execute,非0 执行清理函数
0 ,不执行清理
(4)返回值:无
注:pthread_cleanup_push与pthread_cleanup_pop函数需成对出现;
7、注:
进程可以启动线程,线程也可启动进程(进程中的多线程变为单线程)
8、示例
(1)在循环中批量创建分离态线程,并统计成功创建的线程数量:
#include <pthread.h> // 包含POSIX线程头文件,用于多线程操作
#include <stdio.h> // 包含标准输入输出头文件
#include <stdlib.h> // 包含标准库头文件(含内存分配等函数)
#include <unistd.h> // 包含UNIX系统相关头文件(此处可能未实际使用)
void* th(void* arg) // 线程函数,返回值和参数均为void*指针
{
pthread_detach(pthread_self()); // 将当前线程设置为分离态,线程结束后系统自动回收资源
return NULL; // 线程执行完毕,返回空指针
}
int main(int argc, char** argv) // 主函数,argc为参数个数,argv为参数数组
{
int i = 0; // 循环计数器
pthread_t tid; // 存储线程ID的变量
for (i = 0; i < 50000; i++) // 循环创建50000个线程
{
int ret = pthread_create(&tid, NULL, th, NULL); // 创建线程,传入线程函数th,无参数
if (ret != 0) // 检查线程创建是否失败(非0表示失败)
{
break; // 失败则退出循环
}
printf("%d\n", i); // 打印当前创建的线程序号
}
system("pause"); // 调用系统命令pause(Windows环境暂停程序,防止退出)
return 0; // 主函数正常退出
}
(2)利用线程清理处理机制确保动态分配内存的正确释放:
#include <pthread.h> // 包含POSIX线程相关头文件(线程创建、清理等)
#include <stdio.h> // 包含标准输入输出头文件(printf等)
#include <stdlib.h> // 包含标准库头文件(malloc、free等内存操作)
#include <unistd.h> // 包含UNIX系统相关头文件(此处未实际使用)
#include <string.h> // 包含字符串操作头文件(strcpy等)
void clean(void arg) // 线程清理处理函数,参数为void指针
{
printf("this clean %s\n", (char*)arg); // 打印清理信息,输出传入的字符串
free(arg); // 释放动态分配的内存,避免泄漏
}
void* th(void* arg) // 线程执行函数,返回值和参数均为void指针
{
strcpy((char )arg, "hello world\n"); // 将字符串复制到传入的内存地址(修改arg指向的数据)
printf("th,strcpy over\n"); // 打印线程内操作完成的提示
return NULL; // 线程执行完毕,返回空指针
}
int main(int argc, char *argv) // 主函数,argc为参数个数,argv为参数数组
{
pthread_t tid; // 定义线程ID变量,用于存储创建的线程标识
char p = malloc(50); // 动态分配50字节内存,返回指针p指向该内存块
pthread_cleanup_push(clean, p); // 注册线程清理函数clean,参数为p(内存地址)
pthread_create(&tid, NULL, th, p); // 创建线程,传入线程函数th和参数p(内存地址)
pthread_join(tid, NULL); // 阻塞等待线程tid结束,回收其资源
printf("before pop\n"); // 打印提示,表示即将执行清理函数弹出操作
pthread_cleanup_pop(1); // 弹出清理函数,并执行(参数1表示执行清理函数)
system("pause"); // 调用系统命令pause(Windows环境暂停程序)
return 0; // 主函数正常退出
}