线程概念:
- 在传统操作系统中,进程就是一个运行中程序的描述信息---------pcb,控制着程序的运行:
- linux中并没有为线程设计一个tcb来控制线程的运行
- 在linux下,线程是以进程的pcb模拟实现,也就是说linux下pcb是一个线程
- 在linux下进程实际上是一个线程组------当中包含一个/多个线程
- 因为cpu调度的基本单位是pcb,所以线程是cpu调度的基本单位
- 因为一个程序运行起来救会分配大量资源给线程组,所以进程是资源分配的基本单位
- vfork因为创建了子进程和父进程共用同一个虚拟地址空间,因此不能同时运行
多线程共用同一个虚拟地址空间如何做到同时运行而不会出现问题?
1.同一进程间线程之间独有的数据:
栈 寄存器 errno 信号屏蔽字 线程标识符
2.同一进程间线程之间共享的数据:
数据段,代码段 文件描述符 信号的处理方式 工作路径 用户id,组id
多进程与多线程都可以并发的完成任务,哪个好?:分析优缺点,视场景而定
多线程优点:共享同一个虚拟地址空间
1,线程间通信更加方便
2,线程的创建/销毁成本更低
3,同一个进程间的线程调度成本更低
4,执行粒度更加细致
多线程缺点:缺乏访问控制,健壮性低,一些系统调用和异常会对整个程序产生影响
共同缺点:对临界资源的操作需要考虑更多,编码更加复杂
多进程场景:对主进程安全要求特别高的程序----比如shell,让子进程背锅
线程控制接口:
因为操作系统没有直接提供线程的接口,因此大佬们就封装了一套线程库供我们使用,因此有人称创建的线程是一个用户态线程,在内核当中都有一个轻量级进程调度程序运行。
线程的创建:
每一个线程都是一个pcb--task--struct结构体都有一个pid,但是用户ps查看的进程的时候也只有一个进程,也只有一个PID,这个pid是线程组id
#include<stdio.h>
#include<errno.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
void* start_routine(void* arg){
//void pthread_exit(void* retval);
//retval:线程的退出返回值
//退出的调用线程
//pthread_cancel();
while(1){
printf("i am child thread %s\n",(char*)arg);
sleep(3);
}
pthread_exit(NULL);
//exit(0);
}
int main(){
pthread_t tid;
pthread_t ftid=pthread_self();
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
// void *(*start_routine) (void *), void *arg);
//thread:输出型参数
// 用于获取线程 id
//attr:线程属性
// 一般置 NULL
//start_routinue:线程入口函数
//arg:传给线程入口函数的参数
//返回值:0成功 !0失败(errno)
int ret=pthread_create(&tid,NULL,start_routine,(void*)"hello word");
if(ret!=0){
perror("pthread error");
return -1;
}
sleep(5);
//取消另一个线程
pthread_cancel(tid);
while(1){
printf("i am main pthread:%d\n",pthread_self());
sleep(1);
}
return 0;
}
这里要特别注意:线程标识符 ID(LWP) 和 tid 不是同一个概念 tid是线程在共享区的首地址
线程的等待,分离,终止:
为了防止资源泄露,必须回收线程资源
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<unistd.h>
#include<pthread.h>
//int pthread_join(pthread_t thread,void** retval);
//thread: 指定线程tid,输出型参数
//retval:用于获取线程的退出返回值
//返回值 0(成功) !0(errno)
//功能:等待线程退出获取返回值,回收线程资源
//前提:这个线程必须处于 joinable状态
//int pthread_detach(pthread_t thread);
//thread:要被分离的线程id
//功能:分离一个线程(设置线程的属性由joinable->detach状态)
// 线程退出后系统自动回收资源
// 被分离的线程无法被等待,否则会报错返回
//注意:分离线程的前提是线程的返回值是用户不关心的
void* thr_start(void* arg){
pthread_detach(pthread_self());
pthread_exit("luoyi sb\n");
while(1){
printf("i am thread \n");
sleep(1);
}
return NULL;
}
int main(){
pthread_t tid;
int ret=pthread_create(&tid,NULL,thr_start,(void*)"hello word");
//pthread_detach(tid);
if(ret!=0){
perror("thread create error");
return -1;
}
sleep(1);
char* retval=NULL;
int res=pthread_join(tid,(void**)&retval);
//线程必须处于joinable状态才可以被等待,故
if(res==EINVAL){
printf("thread not waitted\n");
}
printf("retval;%s",retval);
while(1){
printf("i am main thread\n");
sleep(1);
}
return 0;
}
这里特别注意一个问题:线程分离如果放在函数入口处可能会导致在并发操作时线程没有来得及分离而获取到返回值 ,所以一定要先分离 sleep(1) 起到了很重要的作用
线程的存储模型:(深入理解计算机系统)
将变量映射到存储器:
全局变量:定义在函数之外的变量。在运行的时,虚拟存储器的读/写区域只包含每一个全局变量的实例,任何线程都可引用
本地自动变量:定义在函数内部但是没有static属性的变量。在运行的时候,每一个线程的栈都包含他自己所有本地变量的实例
本地静态变量:定义在函数内部并有static属性的变量。和全局变量一样,虚拟存储器的读/写区域只包含在程序中声明的每个本地静态变量的一个实例