一、进程与线程
1、线程了解:
在linux下,程序或可执行文件是一个静态的实体,它只一组指令的集合,没有执行的含义,进程是一个动态的实体,有自己的生命周期,线程是轻量级进程,是操作系统进程调度器可以调度的最小执行单元,传统意义上的进程,只不过是多线程的一种特例(该进程只包含一个线程),一般情况下,一个进程资源可以分成多块(线程)让不同的CPU去执行(由原来的串行变为并行,提高效率)
2、线程内存
进程之间,彼此的地址空间是独立的,但线程存放在进程的地址空间内,会共享内存地址空间,同一个进程的多个线程共享一份全局的内存空间
//共享进程资源 1. ⽂件描述符表 2. 每种信号的处理⽅式(SIG_IGN、 SIG_DFL或者⾃定义的信号处理函数) 3. 当前⼯作⽬录 4. ⽤户id和组id 5. 初始化数据段,未初始化数据段,和动态分配的堆内存段3、共享优势
1、创建线程花费的时间要少于创建进程花费的时间 2、终止线程花费的时间要少于终止进程花费的时间 3、线程之间的上下文切换(同一个进程里不同的线程之间的上下文切换)的开销,要小于进程之间的上下文切换 4、线程之间数据的共享要比进程之间的数据共享简单4、线程结构: 进程:承担分配系统资源的实体,讲究资源独占,并拥有独立的地址空间 线程:CPU调度基本单位,讲究资源共享,每个线程也有自己独立的私有栈结构 (一般默认为8MB)
//私有栈结构 1. 线程id 2. 上下文⽂,包括各种寄存器的值、程序计数器和栈指针 3. 栈空间 4. errno变量 5. 信号屏蔽字 6. 调度优先级5、线程弊端
1. 多线程的进程,因地址空间的共享让该进程变得更加脆弱,只要有一个线程存在问题,就会影响其他线程 2. 线程模型作为一种并发的编辑模型,效率并不一定高,会出现复杂度高,难以测试和定位的问题二、线程创建与标识
#include <pthread.h> int pthread_create(pthread_t *restrict thread,const pthread_attr_t* restrict attr,void *(*start_routine)(void*),void *restrict arg); //第一个参数为pthread_t 类型的指针,线程创建成功的话,会将分配的线程ID填入该指针指向的地址,线程的后续操作将使用该值作为线程的唯一标识 //第二个参数pthread_attr_t类型的指针,该参数可以定制线程的属性,比如可以指定新建线程栈的大小,调度策略等,如果创建的线程无要求,可值为NULL,采用默认属性 //第三个参数是线程需要执行的函数,该线程会执行start_routine函数 //第四个参数新建线程执行的start_routine函数的入参 //函数创建成功,返回0,不成功,返回一个非0 的错误码 //对于pthread_t 类型的ID,会存在线程ID复用的情况(在一个线程退出时,重建的线程很有可能会复用) 函数实例: void* thread_worker(void*) { printf("I am thread worker "); prhread_exit(NULL);//线程内部终止 exit();//进程终止 } pthread_t tid; int ret = 0; ret = pthread_create(&tid,NULL,&thread_worker,NULL); if(ret != 0) //不能用ret<0判断 { } //获取线程ID pthread_t pthread_self(void); // 利用ps -L 查看线程信息 可以通过LWP与NLWP 来查看同一个线程组的线程个数和线程ID信息三、线程的退出
//线程终止的三种方法 1、创建线程时的start_routine函数执行了return,并且返回指定值 2、线程调用了pthread_exit 3、其他函数调用了pthread_cancel函数取消了该线程 //如果线程组中的任意一个线程调用了exit函数,或者主线程在main函数中执行了return语句,那么整个线程组内的所有线程都会终止四、线程的连接
线程退出时会有返回值的
//等待某线程的退出并接收其返回值 int pthread_join(pthread_t thread,void ** retval); //第一个参数要等待的线程ID,第二个参数用来接收返回值 根据等待情况分为两种 1.等待的线程尚未退出,则陷入阻塞 2.线程已经退出,则会将线程的返回值存放在retval指针指向的位置 //线程连接的重要性 如法不连接已退出的线程,就会导致资源无法释放 假定: 1.主线程不执行连接操作,创建的第一个线程退出后,再创建第二个线程 //已经退出的线程,其空间没有被释放,仍然在进程的地址空间内,新创建的进程,没有复用刚才退出的线程的地址空间,导致内存泄露 2.主线程执行连接操作,创建的第一个线程退出后,再创建第二个线程 //线程连接后,第二个线程复用了第一个线程的栈空间,第一个线程退出时会自动调用_free_tcb函数来释放退出线程的资源 要明白的是,系统并不会立即调用munmap来释放退出线程的栈,他们是被后建的线程复用了,在释放栈时,线程库会认为进程可能会再次创建线程,则会将要释放的栈缓存在一个链表(一般默认40MB)中,如果有新的创建请求,县线程库会优先在链表中寻找栈空间合适的栈,有的话,就直接复用。五、线程分离
默认情况下,新创建的线程处于可连接状态,可连接状态的线程退出后,需要对其执行连接操作,否则会使线程资源无法释放,造成资源泄露 如果其他线程并不关心线程的返回值,那么再连执行接操作就会显得多余,这时你还要求在线程退出时释放其资源,无需等待连接就要用到线程的分离
int pthread_detach(pthread_t thread) //将线程设置为分离状态,如果线程已处于分离状态,那么在线程退出时,系统将负责回收线程资源, 可以是线程组内其他线程对目标线程的分离,也可以是线程自己执行函数进行分离 //已分离:并不是指线程失去控制,不归线程组管理,而是指线程退出后,系统会自动释放其资源,若线程组内的任意线程执行了exit()函数,即使是已分离的线程,也会受到影响,一并退出。