创建多个进程(父子进程)有什么缺点?
虽然遵循 “读时共享 写时拷贝” 但仍然存在资源的消耗与浪费
什么是线程?
进程是操作系统分配资源的最小单位
线程是操作系统调度的最小单位
线程是操作系统执行的单位
类比于足球比赛 进程是教练 线程是场上的队员
进程是资源管家 线程去干活
进程有独立的地址空间 而线程没有
线程有权访问所属进程拥有的资源
实际上
无论是创建进程的 fork()
还是创建线程的 pthread_create()
底层实现都是调用同一个 内核函数 clone()
如果是复制原先的地址空间 就创建了进程 类似于深拷贝
如果是共享原先的地址空间 就创建了线程 类似于浅拷贝 (栈空间是不共享的)
线程是轻量级的进程 在Linux下线程的本质就是进程
在Linux底层/内核不区分进程或者线程 两者只是在应用层/上层进行区分
线程的优缺点
优点
1.提高程序的并发性
2.开销比进程小
3.方便数据的通信与共享
缺点
1.库函数不稳定(使用第三方库创建的线程)
2.编程复杂 调试困难
3.对信号的支持较差
线程常用操作
1.引入头文件 #include<pthread.h>
2.链接线程库 gcc XXX.c -pthread
线程号获取
pthread_t pthread_self(void);
线程号只在所属的进程中唯一
一个进程至少包含一个线程 默认有一个线程
返回线程ID 通常用 pthread_t (无符号长整形%lu) 数据类型表示
比较线程ID
int pthread_equal(pthread_t t1,pthread_t t2);
比较两个线程ID是否相同
有时线程ID也会用结构体表示
所以不能直接用运算符比较t1 t2
应当用函数比较 关注代码的可移植性
创建线程
int pthread_create(...);
创建线程 成功则返回0
失败时不可以使用perror()查看错误
int pthread_create(pthread_t *thread, //传出线程标识符地址
const pthread_attr_t * attr, //线程属性结构体地址 默认设置为NULL
void *(*start_routine)(void *), //回调函数 线程启动后执行的任务
void *arg); //传给回调函数的参数(上一行的参数)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
void *fun(void *arg)
{
printf("new thread tid: %lu\n", pthread_self());
printf("get number %d\n", *(int*)arg);
return NULL;
}
int main(void)
{
int ret = -1;
pthread_t tid = -1;
int number = 520;
ret = pthread_create(&tid, NULL, fun, &number);
if(0 != ret)
{
printf("failed.\n");
return 1;
}
printf("success.\n");
printf("main thread tid: %lu\n", pthread_self());
getchar();
return 0;
}
注意:
当 主线程(main thread) return 0 时 认为进程已经结束
因此 创建的新线程(new thread)也会被立即终止
为了确保子线程执行完毕 需要阻塞主线程的退出
当一个进程创建了多个线程的时候
多个线程是交替且同时运行的 运行顺序跟CPU调度相关 无法预测
运行效果:
线程的资源共享与回收
线程共享进程的数据段(全局变量)和堆区数据
线程不共享栈空间 每个线程有一个独立的栈空间
进程使用wait()和waitpid()进行回收资源
线程使用
int pthread_join(pthread_t thread, void **retval);
此函数会阻塞至线程结束 并回收线程资源
如果线程结束后不被回收 则会变成僵尸线程
其中 thread 是被等待的线程ID
retval 传出线程回调函数返回的地址
成功回收 函数返回0
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
void *fun(void *arg)
{
printf("new thread tid: %lu\n", pthread_self());
return (void *)0x520;
}
int main(void)
{
int ret = -1;
pthread_t tid = -1;
void *retp = NULL;
ret = pthread_create(&tid, NULL, fun, NULL);
if(0 != ret)
{
printf("failed.\n");
return 1;
}
printf("main thread running...\n");
ret = pthread_join(tid, &retp);
if(0 != ret)
{
printf("failed.\n");
return 1;
}
printf("retp: %p\n", retp);
printf("main thread exit.\n");
return 0;
}
运行效果:
线程的分离、退出、取消
分离 int pthread_detach(pthread_t thread);
可以把线程设定为
线程终止后立即由内核回收资源 不保留终止状态
这样就不必(也不能)调用pthread_join()进行资源回收
成功约定 返回0
退出 void pthread_exit(void *retval);
在线程的回调函数中写 pthread_exit(NULL); 实现线程退出
不能在线程中调用exit(0)退出线程 否则整个进程将被终止
取消 int pthread_cancel(pthread_t thread);
一个线程可以杀死同进程中的另一个线程
但不稳定 有延时(不是立即杀死)需要等待一个取消点(如系统调用)
成功返回0
线程的属性
创建线程时调用的int pthread_create(...);
其中第二个参数是一个结构体
1.线程的分离状态
2.线程栈的大小
3.线程栈的警戒缓冲区大小
4.线程栈的最低地址
属性值必须通过相关函数操作
pthread_attr_init();
必须在创建线程之前调用
int ret = -1;
pthread_attr_t attr;
ret = pthread_attr_init(&attr);
//设置相关状态
//创建对应线程
ret = pthread_attr_destroy(&attr);
线程的注意点总结
1.如果希望主线程退出其他线程不退出
主线程中调用 pthread_exit() 不能 return 0
2.避免僵尸线程
调用 pthread_join() 回收
被join的线程不应当返回栈空间的值
可能在函数返回前 join函数已经将栈空间释放
或者 设置线程分离 调用 pthread_detach()
或者 创建线程前 初始化并设置好线程属性
3.避免使用信号 否则使用多进程