线程相关的概念
线程概念:
1.先说进程:传统操作系统中使用PCB来描述一个程序的运行- - - PCB就是进程
2.Linux下PCB用来模拟实现线程,因此Linux下的线程是一个轻量级的进程
3.而且这些轻量级的进程因为共用大部分进程资源,相较于传统的进程更加轻量化
进程是资源分配的基本单位,线程是CPU调度的基本单位
多个PCB共用同一个虚拟地址空间,为什么不会发生调用栈混乱呢?
线程之间的独有与共享
独有:栈, 寄存器, 信号屏蔽字(阻塞的信号集合), errno(错误信息的全局变量), 线程ID, 调度优先级
共享:共享虚拟地址空间, 文件描述符, 信号处理方式,当前工作路径, 用户ID/组ID
多任务执行时,既可以用多进程,也可以用多线程,用哪个更好?
答:
1.需要频繁创建销毁的优先用线程
2.需要进行大量计算的优先使用线程
3.强相关的处理用线程,弱相关的处理用进程
多线程处理任务的优点
1.线程间通信除了与进程通信相同的之外还有全局数据/传参,这使得线程间通信更加方便
2.创建/销毁一个线程的成本相较于进程更低
3.线程间的调度相较于进程更少
缺点
1.线程之间缺乏访问控制,有些系统调用/异常针对的是整个进程;稳定性相较于进程更低
2.性能损失,增加了额外的同步与调度开销的同时,可用的资源不变,这时容易造成性能损失
3.编程难度高
所以像shell这种对主程序稳定安全性要求高的需要用多进程处理,让子进程处理命令,出现异常后即使子进程退出,对终端的影响也不大,但是如果用多线程,一个线程出现问题,整个进程就退出了,终端就凉凉了
线程控制
Linux中并没有提供线程的控制系统调用接口.因此有一群大佬封装了一个线程控制接口库,只用库函数创建的线程称之为用户态线程,用户态线程在内核中使用一个轻量级进程实现调度
即Linux下的线程:用户态线程+轻量级进程
线程创建
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
参数
pthread_t *thread:输出型参数,用于获取线程ID(也就是线程地址空间在虚拟地址空间的首地址)
const pthread_attr_t *attr:设置线程属性,通常置空
start_routine:线程的入口函数,返回任意类型的参数
arg:作为入口函数的实参传入
返回值:成功返回0;失败返回错误码
简单实现线程的创建,每个线程都可以运行
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void* Ret(void *arg)
{
while(1) {
printf("i am same thread----%s\n", (char *)arg);
sleep(1);
}
return NULL;
}
int main()
{
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
// void *(*start_routine) (void *), void *arg);
pthread_t tid;
char *ret = "this is input ret";
int ret = pthread_create(&tid, NULL, Ret, (void*)ret);
if (ret != 0) {
printf("pthread create error\n");//打印线程ID,为一个地址
return -1;
}
printf("tid:%p\n", tid);
while(1) {
printf("i am main thread-----\n");
sleep(1);
}
return 0;
}
注意创建多线程时,用到了封装的库,编译时默认链接的库只有lib,所以想要创建成功的话需要链接静态库(libpthread.so),所以在编译时需要这样写
gcc pthread_create.c -o create -pthread
线程中id的讨论
tid:线程地址空间首地址
pcb-->轻量级进程id
pcb-->进程id,也就是线程组id,默认等于首线程id
tid是线程id,也是线程地址空间的首地址,线程地址空间:存放着线程在用户态的描述信息,线程栈,线程的局部存储
线程终止
线程终止的三种方法:
1.从线程函数中return,但是不能从main函数中return,这样退出的是进程,会导致所有线程退出
2.调用exit终止自己,谁调用谁退出
void pthread_exit(void *retval);
其中retval是线程的退出返回值
注意: return 和 pthread_exit 返回的指针必须是全局的或者是用malloc分配的,不能再线程函数的栈上分配,因为当其他线程得到这个返回值时线程函数已经退出了
3.用pthread_cancel取消一个正在执行的线程
int pthread_cancel(pthread_t thread);
其中thread是要取消的线程ID
线程退出后,默认不会自动释放资源(保存自己的退出结果在线程独有的地址空间中),因此也会造成资源泄露
主线程退出,其他线程依然可以正常运行
线程等待
等待线程退出,获取退出线程的返回结果,释放退出线程资源
一个线程创建后,默认有一个属性 joinable ;处于joinable属性的线程退出后为了保存返回值,不会自动释放资源,需要被其他线程等待,才能释放资源,否则就会造成资源泄露
int pthread_join(pthread_t thread,void **retval);
功能:阻塞等待指定线程退出,通过retval获取返回值
其中thread是线程ID,retval指向一个指针,这个指针指向线程的返回值
返回值:成功返回0,失败返回错误码
线程分离
线程分离就是将线程的joinable属性改为detach属性
处于detach属性的线程,线程退出后将自动回收资源,所以这个线程不需要被等待
pthread_detach(pthread_t tid);
线程分离的适用场景:对线程的返回值不关心的场景
线程分离可以在任意线程中实现