线程的概念 以及 线程vs进程
线程控制,线程创建
线程终止,线程等待
线程分离
可重入VS线程安全
#什么是线程?
1.一个程序里的一个执行流,是一个进程内部的控制序列。
创建一个线程就是在内部当中创建一个PCB,这个PCB指向了进程的虚拟空间(线程其实在当前的内核当中是没有概念的,我们认为的线程其实是一个轻量级进程LWP)
2.一切进程至少都有一个执行线程.
3.线程在进程内部运行,本质是在进程地址空间内运行。
4.透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流。
多线程可以提高程序的运行效率。
##线程的优点
1.创建线程的代价要比创建一个新进程小的多。
2.线程之间的切换需要操作系统的工作要少很多
3.线程占用的资源比进程少的多
4.能充分利用多处理器的可并行数量(多线程并行)
##线程缺点
1.性能损失
增加了额外的同步和调度开销,而可用的资源不变
2.健壮性降低/鲁棒性降低
多线程的程序中有多个执行流,一旦一个执行流导致异常,就会影响整个进程,也就是说线程之间是缺乏保护的
3.缺乏访问控制
不同的执行流的执行顺序会对结果造成影响
4.编程难度提高
##线程异常
1.单个执行流如果出现除零,野指针问题导致线程崩溃
2.线程是进程的执行分支,线程出现异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出。
#进程vs线程
进程是资源分配的基本单位
线程是操作系统调度器的的最小基本单位
**线程共享进程数据,**但也有自己的一部分数据:独有:线程ID,一组寄存器,栈,errno,信号屏蔽字,调度优先级
共享:进程的虚拟空间,代码段,数据段,文件描述符表,当前进程的工 作目录,用户id和组id
#线程控制
POSIX线程库(库函数接口):
与线程相关的函数绝大部分都是以“pthread_”开头
引入#include
链接线程库时使用编译命令“-lpthread”
##1.创建线程
//创建一个线程
int pthread_create(pthread_t *thread,const pthread_arr_t *arr,void *(*start_routine)(void *),void *arg);
//thread:返回线程ID,出餐,其实是一个地址
//attr:设置线程属性,NULL表示使用默认属性(线程是否分离,线程栈的大小,线程栈的位置,调度策略等。。。)
//start_routine:函数地址,线程启动后要执行的函数
//arg:传给线程启动函数的参数,可以是默认类型,也可以是自定义类型(如果传入的参数是临时变量,意味着在入口函数中使用时,又可能会访问,导致访问非法地址,导致程序崩溃)
//返回值:成功返回0,失败返回错误码
一般传统函数,成功返回0,失败返回-1,并对全局变量errno赋值以指示错误。pthreads函数出错时不会设置全局变量errno(耳大部分POSIX函数会这样做),而是将错误代码通过函数值返回。
pthreads同样也提供了线程内errno变量,以支持其他使用errno的代码。对于pthreads函数的错误,建议通过返回值判定,因为读取返回值要比读取线程内errno变量的开销小。
##2.线程终止
//线程终止
void pthread_exit(void *value_ptr);//谁调用谁退出
//value_ptr:不要指向一个局部变量
//返回值:无返回值,跟进程一样,线程结束的时候无法返回它的调用者(自身)
//取消一个任意执行中的进程
int pthread_cancel(pthread_t thread);
//thread:线程ID
//返回值:成功返回0,失败返回错误码
注意:pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配–>因为当其他线程得到这个返回指针时线程函数已经退出了。
主线程调用pthread_exit退出时,进程不会退出,但是主线程的状态变成了Zl+状态(ZL+:僵尸轻量级进程前台)
##3线程等待
//等待线程结束
int pthread_join(pthread_t thread,void **value_ptr);
//thread:线程ID
//value_ptr:指向一个指针,后者指向线程的返回值()线程终止的信息
//返回值:成功返回0,失败返回错误码
为什么需要线程等待?(阻塞等待,防止内存泄漏)
1.已经退出的线程,空间未被释放,仍然在进程的地址空间内。
2.创建新的进程不会赋值刚才退出线程的地址空间。
调用该函数的线程将挂起等待,知道id为thread的线程终止。
thread线程以不同的方式终止,通过pthread_join得到的终止状态是不同的。
1.如果thread线程通过return返回,value_ptr指向的单元里存放的是thread函数的返回值。
2.如果thread线程被其他pthread_cancel异常终止掉,value_ptr指向的是单元里存放的常熟PTHREAD_CANCELED.
3.如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的是单元里存放的是传给pthread_exit的参数。
4.如果thread线程的终止状态不感兴趣,可以传NULL给value_ptr参数。
##4.线程分离
int pthread_detach(pthread_t thread);
//pthread_detach(pthread_self());线程自己分离
注意:1.默认情况下,新创建的线程是joinable(joinable需要其他线程来释放资源)的,线程退出时,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
2.如果不关心线程的返回值,join是一种负担,线程 是分离属性时,当线程退出时,这时我们可以告诉系统,自动释放线程资源,不需要其他进程来回收。
3.joinable和分离是冲突的,一个线程不能既是joinable又是分离的。
#可重入VS线程安全
##概念
1.线程安全:多个线程并发同一段代码,不会出现不同的结果。
常见对全局变量或者静态变量进行操作,并没有保护锁的情况下,会出现问题。
2.重入:同一个函数被不同执行流调用,当前一个线程还没调用完,就有其他执行流再次进入。
3.可重入函数:一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题。否则,是不可重入函数。
##常见的线程不安全的情况
1.不保护共享变量的函数
2.函数状态随着被调用,状态发生变化的函数
3.返回指向静态变量指针的函数
4.调用线程不安全函数的函数
##常见的线程安全的情况
1.每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限。
2.类或者接口对于线程来说都是原子操作。
3.多个线程之间切换不会导致该接口的执行结果存在二义性。
##常见不可重入的情况
1.调用malloc/free函数,因为malloc函数是全局链表来管理堆的
2.调用标准I/O库函数,标准I/O库有很多实现都以不可重入的方式使用全局数据结构
3.可重入函数体内使用了静态的数据结构
##常见可重入的情况
1.不使用全局变量或静态变量
2.不使用malloc或者new开辟的空间
3.不调用不可重入函数
4.不返回静态或者全局数据,所有数据都是函数调用者提供
5.使用本地数据,或者通过制作全局函数本地拷贝来保护全局变量
##可重入与线程安全联系
1.函数是可重入的,那就是线程是安全的
2.函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题
3.如果一个函数中有全局变量,那么这个函数即不是线程安全也不是可重入的。
##可重入与线程安全区别
1.可重入函数是线程安全的一种
2.线程安全不一定是可重入的,而可重入函数一定是线程安全的。
3.如果对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的。