-
2020-07-21 11:51:32
多线程概念:
进程是系统中程序执行和资源分配的基本单位。每个进程有自己的数据段、代码段和堆栈段。这就造成进程在进行切换等操作时都需要有比较负责的上下文切换等动作。为了进一步减少处理器的空转时间支持多处理器和减少上下文切换开销,也就出现了线程。(就是防止进程某一部分等待,导致进程白白切换)
线程的分类:
1)用户级线程:主要解决上下文切换问题,调度算法和调度过程全部由用户决定,在运行时不需要特定的内核支持。
缺点是无法发挥多处理器的优势2)核心级线程:允许不同进程中的线程按照同一相对优先调度方法调度,发挥多处理器的并发优势
现在大多数系统都采用用户级线程和核心级线程并存的方法。一个用户级线程可以对应一个或多个核心级线程,也就是“一对一”或“一对多”模型。线程创建的 Linux 实现
Linux 的线程是通过用户级的函数库实现的,一般采用 pthread 线程库实现线程的访问和控制。它用第 3 方 posix标准的 pthread,具有良好的可移植性。 编译的时候要在后面加上 –lpthread
---------------------------------------创建 -------------------------- 退出----------------------------等待
多进程 ----------------------------fork() --------------------------exit() ----------------------------wait()
多线程------------------------ pthread_create ---------pthread_exit() ----------------------pthread_join()//1、线程的创建
#include <pthread.h> int pthread_create(pthread_t* thread, pthread_attr_t * attr, void *(*start_routine)(void *), void * arg); void pthread_exit(void *retval); 通常的形式为: pthread_t pthid; pthread_create(&pthid,NULL,pthfunc,NULL);或 pthread_create(&pthid,NULL,pthfunc,(void*)3); pthread_exit(NULL);或 pthread_exit((void*)3);//3 作为返回值被后面的 pthread_join 函数捕获。 函数 pthread_create 用来创建线程。返回值:成功,则返回 0;失败,则返回-1。各参数描述如下: 参数 thread 是传出参数,保存新线程的标识; 参数 attr 是一个结构体指针,结构中的元素分别指定新线程的运行属性,attr 可以用 pthread_attr_init 等函数设置各成员的值,但通常传入为 NULL 即可; 参数 start_routine 是一个函数指针,指向新线程的入口点函数,线程入口点函数带有一个 void *的参数由 pthread_create 的第 4 个参数传入; 参数 arg 用于传递给第 3 个参数指向的入口点函数的参数,可以为 NULL,表示不传递。 函数 pthread_exit 表示线程的退出。其参数可以被其它线程用 pthread_join 函数捕获。
//2、线程的等待退出
线程本身的资源必须通过其它线程调用 pthread_join 来清除,这相当于多进程程序中的 waitpid。线程从入口点函数自然返回,或者主动调用 pthread_exit()函数,都可以让线程正常终止 线程从入口点函数自然返回时,函数返回值可以被其它线程用 pthread_join 函数获取 pthread_join 原型为: #include <pthread.h> int pthread_join(pthread_t pthid, void **thread_return); 1). 该函数是一个阻塞函数,一直等到参数 pthid 指定的线程返回;与多进程中的 wait 或 waitpid 类似。 thread_return 是一个传出参数,接收线程函数的返回值。如果线程通过调用 pthread_exit()终止,则 pthread_exit()中的参数相当于自然返回值,照样可以被其它线程用 pthread_join 获取到。 2)、该函数还有一个非常重要的作用,由于一个进程中的多个线程共享数据段,因此通常在一个线程退出后,退 出线程所占用的资源并不会随线程结束而释放。如果 th 线程类型并不是自动清理资源类型的,则 th 线程退出后,
#include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <sys/stat.h> void *fun(void *p) //参数的值为 123 { int i = 0; for(; i<10; i++){ printf("world,p = %d \n", (int)p); sleep(1); } pthread_exit(200);//线程结束,返回一个200的值 } int main() { pthread_t id, id2; int a=0,i=0; pthread_create(&id, NULL, fun, (void *)123 ); for(; i<10; i++){ printf("hello \n"); sleep(1); } pthread_join(id, &a);//接收来自线程ID为id的返回的值 printf("a=%d \n",a); return 0; }
gcc -o a a.c -lpthread
//3、程序的取消
int pthread_cancel(pthread_t thread);
参数:thread线程的ID线程也可以被其它线程杀掉,在 Linux 中的说法是一个线程被另一个线程取消(cancel)。
线程取消的方法是一个线程向目标线程发 cancel 信号,但是如何处理 cancel 信号则由目标线程自己决定,目标线程或者忽略、或者立即终止、或者继续运行至 cancelation-point(取消点)后终止。
取消点:
根据 POSIX 标准,sleep,pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及 read()、write()等会都会引起取消点 Cancelation-point。只要出现取消点,线程才从哪个位置退出出来
#include <stdio.h> #include <pthread.h> #include <malloc.h> void* threadfunc(void *args) { char *p = (char*)malloc(10); //自己分配了内存 int i = 0; for(; i < 10; i++){ printf("hello,my name is lirongye!\n"); sleep(1); } free(p); //如果父线程中没有调用 pthread_cancel,此处可以执行 printf("p is freed\n"); pthread_exit((void*)3); } int main() { pthread_t pthid; pthread_create(&pthid, NULL, threadfunc, NULL); int i = 1; //父线程的运行次数比子线程的要少,当父线程结束的时候,如果没有 pthread_join 函数 //等待子线程执行的话,子线程也会退出。 for(; i < 5; i++) { printf("hello,nice to meet you!\n"); sleep(1); if(i % 3 == 0) pthread_cancel(pthid); //表示当 i%3==0 的时候就取消子线程,该函数将导致子线程直接退 //出,不会执行上面紫色的 free 部分的代码,即释放空间失败。要想释放指针类型的变量 p,此时必须要用 //pthread_cleanup_push 和 pthread_cleanup_pop 函数释放空间,见后面的例子 } int retvalue = 0; pthread_join(pthid,(void**)&retvalue); //等待子线程释放空间,并获取子线程的返回值 printf("return value is :%d\n",retvalue); return 0; }
4、线程终止清理函数
不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证线程终止时能顺利的释放掉自己所占用的资源,特别是锁资源,就是一个必须考虑解决的问题。最经常出现的情形是资源独占锁的使用:线程为了访问临界共享资源而为其加上锁,但在访问过程中该线程被外界取消,或者发生了中断,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于资源释放的编程。在 POSIX 线程 API 中提供了一个 pthread_cleanup_push()/pthread_cleanup_pop()函数对用于自动释放资源–从pthread_cleanup_push() 的 调 用 点 到 pthread_cleanup_pop() 之 间 的 程 序 段 中 的 终 止 动 作 都 将 执 行pthread_cleanup_push()所指定的清理函数。API 定义如下:
void pthread_cleanup_push(void (*routine) (void *), void *arg)
void pthread_cleanup_pop(int execute)
pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的栈结构管理
void routine(void *arg)函数在调用 pthread_cleanup_push()时压入清理函数栈,多次对 pthread_cleanup_push()
的调用将在清理函数栈中形成一个函数链,在执行该函数链时按照压栈的相反顺序弹出。execute 参数表示程序自然执行到 pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数,为 0 表示不执行,非 0 为执行;这个参数并不影响异常终止时清理函数的执行。
pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式实现的,这是 pthread.h 中的宏定义:#define pthread_cleanup_push(routine,arg) \ { struct _pthread_cleanup_buffer _buffer; \ _pthread_cleanup_push (&_buffer, (routine), (arg)); #define pthread_cleanup_pop(execute) \ _pthread_cleanup_pop (&_buffer, (execute)); }
可见,pthread_cleanup_push()带有一个"{",而 pthread_cleanup_pop()带有一个"}",因此这两个函数必须成对出现,
且必须位于程序的同一级别的代码段中才能通过编译。
pthread_cleanup_pop 的参数 execute 如果为非 0 值,则按栈的顺序注销掉一个原来注册的清理函数的时候,会执行该函数;当 pthread_cleanup_pop()函数的参数为 0 时,仅仅在线程调用 pthread_exit 函数或者其它线程对本线程调用 pthread_cancel 函数时,才在弹出“清理函数”的同时执行该“清理函数”。#include <stdio.h> #include <pthread.h> #include <malloc.h> void freemem(void * args) { free(args); printf("clean up the memory!\n"); } void* threadfunc(void *args) { char *p = (char*)malloc(10); //自己分配了内存 pthread_cleanup_push(freemem,p); //就相当于注册一个函数,在被取消的时候能调用这个函数 int i = 0; for(; i < 10; i++){ printf("hello,my name is lirongye!\n"); sleep(1); } free(p); //如果父线程中没有调用 pthread_cancel,此处可以执行 printf("p is freed\n"); pthread_exit((void*)3); pthread_cleanup_pop(0); } int main() { pthread_t pthid; pthread_create(&pthid, NULL, threadfunc, NULL); int i = 1; for(; i < 5; i++) { printf("hello,nice to meet you!\n"); sleep(1); if(i % 3 == 0) pthread_cancel(pthid); } int retvalue = 0; pthread_join(pthid,(void**)&retvalue); //等待子线程释放空间,并获取子线程的返回值 printf("return value is :%d\n",retvalue); return 0; }
更多相关内容 -
Linux多线程服务端编程,高清无水印!~
2022-03-05 12:58:12Linux多线程服务端编程,高清无水印!~ -
Linux多线程编程小结
2021-01-20 14:52:09Linux进程创建一个新线程时,线程将拥有自己的栈(由于线程有自己的局部变量),但与它的创建者共享全局变量、文件描写叙述符、信号句柄和当前文件夹状态。 Linux通过fork创建子进程与创建线程之间是有差别的... -
linux多线程编程.pdf
2021-09-27 13:02:58linux多线程编程.pdf -
Linux多线程服务端编程
2019-03-03 21:51:03本书主要讲述采用现代C++在x86-64 Linux上编写多线程TCP网络服 务程序的主流常规技术, 这也是我对过去5年编写生产环境下的多线程 服务端程序的经验总结。 本书重点讲解多线程网络服务器的一种IO模 型, 即one loop ... -
linux多线程demo
2021-02-18 09:44:44linux多线程demo 使用cmake编译 -
Linux 多线程实现生产者消费者模式.pdf
2021-08-20 15:07:22Linux 多线程实现生产者消费者模式.pdf -
Linux多线程编程知识点总结(C语言)(csdn)————程序.pdf
2021-12-01 22:43:50Linux多线程编程知识点总结(C语言)(csdn)————程序 -
实验二:Linux多线程创建.docx
2021-04-13 13:23:51操作系统的第二个实验,Linux多线程创建 -
Linux多线程服务端编程-陈硕.pdf
2019-07-04 20:04:47本书主要讲述采用现代C++ 在x86-64 Linux 上编写多线程TCP 网络服务程序的主流常规技术,重点讲解一种适应性较强的多线程服务器的编程模型,即one loop per thread。这是在Linux 下以native 语言编写用户态高性能... -
Linux多线程服务端编程:使用muduo C++网络库(陈硕 著).pdf
2019-06-04 09:01:51Linux多线程服务端编程:使用muduo C++网络库(陈硕 著).pdf -
linux 多线程安全定时器
2017-08-16 22:55:57timerfd的定时器和epoll监听 比较通用 里面有makefile 直接编译即可 -
Linux多线程编程_linux多线程_Linux多线程;应用笔记_columnc9g_
2021-10-03 12:22:17Linux 多线程编程笔记——简单、好记、好用说明:本代码参考麦子学院魏杰老师的Linux多线程编程 视频教程,整理有参考网上的做出修改,自己也可以参考网上比较好的例程 -
linux多线程教程.zip
2021-01-22 21:16:38linux多线程教程.zip -
linux多线程+UDP网络通信(总结+程序)
2017-11-11 13:15:08linux系统下建立多线程程序设计,完成UDP网络通信的发送与接收,包括总结与源代码,实测效果可见链接https://blog.csdn.net/zxp121127/article/details/78506081 -
Linux多线程
2022-03-17 14:47:22文章目录线程概念什么是线程线程的优点线程的缺点线程异常线程用途Linux进程VS线程进程和线程进程的多个线程共享Linux线程控制POSIX线程库创建线程线程终止pthread_exitpthread_cancel 线程概念 什么是线程 在一个...文章目录
线程概念
什么是线程
- 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”
- 一切进程至少都有一个执行线程
- 线程在进程内部运行,本质是在进程地址空间内运行
- 在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化
- 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流
现在我们创建“进程“,不独立创建地址空间,用户级页表,甚至不进行IO将程序的数据和代码加载到内存,我们只创建task_struct,然后让新的PCB,指向和老的PCB指向同样的mm_ struct。然后,通过合理的资面分配(当前进程的资源),让每个task_struct都能使用进程的一部分资源 ! 此时我们的每个PCB被CPU调度的时候,执行的‘粒度’比原始进程执行的‘粒度’要更小一些!注意: Linux下并不存在真正的多线程!而是用进程模拟的!
操作系统中存在大量的进程,一个进程内又存在一个或多个线程,操作系统需要对这些线程进行管理。如果要支持真的线程一定会提高设计操作系统的复杂程度。在Linux看来,描述线程的控制块和描述进程的控制块是类似的,因此Linux并没有重新为线程设计数据结构,而是直接复用了进程控制块,所以我们说Linux中的所有执行流都叫做轻量级进程。
但也有支持真的线程的操作系统,比如Windows操作系统,因此Windows操作系统系统的实现逻辑一定比Linux操作系统的实现逻辑要复杂得多。
线程的优点
- 创建一个新线程的代价要比创建一个新进程小得多
- 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
- 线程占用的资源要比进程少很多
- 能充分利用多处理器的可并行数量
- 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
计算密集型: 执行流的任务主要以计算为主。比如加密解密、大数据查找等为主。
IO密集型: 执行流的任务主要以IO为主。比如刷磁盘、访问数据库、访问网络等为主。线程的缺点
性能损失 一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
健壮性降低 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
缺乏访问控制 进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
编程难度提高 编写与调试一个多线程程序比单线程程序困难得多
线程异常
- 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
- 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出
线程用途
- 合理的使用多线程,能提高CPU密集型程序的执行效率
- 合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)
Linux进程VS线程
进程和线程
- 进程是资源分配的基本单位
- 线程是调度的基本单位
- 线程共享进程数据,但也拥有自己的一部分数据
(1)线程ID
(2)一组寄存器。(存储每个线程的上下文信息)
(3)栈。(每个线程都有临时的数据,需要压栈出栈)
(4)errno。(C语言提供的全局变量,每个线程都有自己的)
(5)信号屏蔽字。
(6)调度优先级。进程的多个线程共享
因为线程是在同一地址空间,所以代码段(Text Segment)、数据段(Data Segment)都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到。
除此之外,各线程还共享以下进程资源和环境:
- 文件描述符表
- 每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
- 当前工作目录
- 用户id和组id
进程和线程的关系如下图:
Linux线程控制
POSIX线程库
- 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
- 要使用这些函数库,要通过引入头文<pthread.h>
- 链接这些线程函数库时要使用编译器命令的“-lpthread”选项
创建线程
创建函数: pthread_create
功能: 创建一个新的线程
函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);
参数
- thread:返回线程ID
- attr:设置线程的属性,attr为NULL表示使用默认属性
- start_routine:是个函数地址,线程启动后要执行的函数
- arg:传给线程启动函数的参数
返回值: 成功返回0;失败返回错误码
错误检查:
- 传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
- pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通过返回值返回。
- pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值业判定,因为读取返回值要比读取线程内的errno变量的开销更小。
使用示例:
#include<iostream> #include<pthread.h> #include<unistd.h> using namespace std; void *thread_run(void* args) { while(true){ cout<<(char*)args<<endl; sleep(2); } return nullptr; } int main() { pthread_t tid; pthread_create(&tid,nullptr,thread_run,(void*)"thread 1"); while(true){ cout<<"main thread is running..."<<endl; sleep(1); } return 0; }
运行结果
当我们用ps axj命令查看当前进程的信息时,虽然此时该进程中有两个线程,但是我们看到的进程只有一个,因为这两个线程都是属于同一个进程的。
我们可以使用ps -aL命令,显示当前的轻量级进程。
注意: 在Linux中,应用层的线程与内核的LWP(Light Weight Process)是一一对应的,实际上操作系统调度的时候采用的是LWP,而并非PID,只不过我们之前接触到的都是单线程进程,其PID和LWP是相等的,所以对于单线程进程来说,调度时采用PID和LWP是一样的。让主线程创建一批新线程
让主线程一次性创建五个新线程,每个线程都去执行
#include<iostream> #include<pthread.h> #include<unistd.h> #include<cstdio> using namespace std; // void* 是系统设计的一个通用接口 void *ThreadRoutine(void* args) { int i=*(int*)args; delete (int*)args; int cnt=0; while(cnt<5){ cout<<"thread index : "<<i<<"count : "<<cnt<<endl; sleep(1); cnt++; } return nullptr; } int main() { #define NUM 5 pthread_t tids[NUM]; for(auto i=0;i<NUM;i++){ int* p=new int(i); pthread_create(tids+i,nullptr,ThreadRoutine,p); } while(true){ cout<<"main thread is running..."<<endl; sleep(1); } return 0; }
运行结果如下
线程终止
终止某个线程(不是终止整个进程)有三种方法
- 线程函数return。
- 调用pthread_exit函数终止自己。
- 调用pthread_cancel函数终止同一进程中的另一个线程。
pthread_exit
功能: 终止线程
函数原型void pthread_exit(void *retval);
参数: retval:线程退出时的退出码信息。
注意:
- 该函数无返回值,类似进程,线程结束时无法返回它的调用者(自身)。
- pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其他线程得到这个返回指针时,线程函数已经退出了。
例,下面代码中,使用pthread_exit函数终止线程,并将线程的退出码设置为10。
#include<iostream> #include<pthread.h> #include<unistd.h> #include<cstdio> using namespace std; // void* 是系统设计的一个通用接口 void *ThreadRoutine(void* args) { int i=*(int*)args; delete (int*)args; int cnt=0; while(cnt<5){ cout<<"thread index : "<<i<<"count : "<<cnt<<endl; sleep(1); cnt++; } pthread_exit((void*)10); } int main() { #define NUM 5 pthread_t tids[NUM]; for(auto i=0;i<NUM;i++){ int* p=new int(i); pthread_create(tids+i,nullptr,ThreadRoutine,p); } while(true){ cout<<"main thread is running..."<<endl; sleep(1); } return 0; }
注意: 不能使用exit函数终止线程,exit函数的作用是终止进程,线程调用exit函数也是终止整个进程。
pthread_cancel
功能: 取消某一个线程
函数原型int pthread_cancel(pthread_t thread);
参数: thread:要取消线程的ID
返回值: 线程取消成功返回0,失败返回错误码。pthread_cancel需要获取线程的ID,获取进程ID有两种方法
- 创建线程时通过输出型参数获得。
- 通过调用pthread_self函数获得。
pthread_self函数的函数原型如下:
pthread_t pthread_self(void);
调用pthread_self函数即可获得当前线程的ID
下面代码先让主线程休息,新线程跑,再依次回收新线程。
#include<iostream> #include<pthread.h> #include<unistd.h> #include<cstdio> using namespace std; // void* 是系统设计的一个通用接口 void *ThreadRoutine(void* args) { int i=*(int*)args; delete (int*)args; while(true){ cout<<"thread index : "<<i<<"count : "<<cnt<<endl; sleep(1); } } int main() { #define NUM 2 pthread_t tids[NUM]; for(auto i=0;i<NUM;i++){ int* p=new int(i); pthread_create(tids+i,nullptr,ThreadRoutine,p); } sleep(5); for(auto i=0;i<NUM;i++){ pthread_cancel(tids[i]); cout<<"thread : "<< tids[i]<<" been cancel!"<<endl; sleep(1); } while(true){ cout<<"main thread is running..."<<endl; sleep(1); } return 0; }
运行结果如下
虽然线程可以自己取消自己,但一般不这样做,我们往往是用于一个线程取消另一个线程,常用的有主线程取消新线程。
注意: 新线程也是可以取消主线程的,取消后新线程继续运行,主线程不再往下运行。一般不建议这样做,新线程需要主线程来回收,如果干掉主线程,新线程无法回收会造成内存泄漏。
线程等待
为什么需要线程等待?
- 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
- 创建新的线程不会复用刚才退出线程的地址空间。
pthread_join
功能: 等待线程结束
函数原型int pthread_join(pthread_t thread, void **value_ptr);
参数:
- thread:线程ID
- value_ptr:它指向一个指针,后者指向线程的返回值
返回值: 成功返回0;失败返回错误码
调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
- 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
- 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_ CANCELED。
- 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
- 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。
注意: PTHREAD_CANCELED实际上就是头文件<pthread.h>里面的一个宏定义,它的值本质就是-1。
例:下面代码中等待线程退出后,打印线程的信息
#include<iostream> #include<unistd.h> #include<pthread.h> using namespace std; #define NUM 3 void* Routine(void* args) { int cnt=3; while(cnt){ cout<<"thread : "<<pthread_self()<<" | count : "<<cnt<<" | is running!"<<endl; cnt--; sleep(1); } return nullptr; } int main() { pthread_t tids[NUM]; for(auto i=0;i<NUM;i++){ pthread_create(&tids[i],nullptr,Routine,nullptr); } cout<<"main thread join begin..."<<endl; for(auto i=0;i<NUM;i++){ if(0==pthread_join(tids[i],nullptr)){// 不关心线程的退出信息时可将pthread_join函数的第二次参数设置为NULL cout<<"thread "<<tids[i]<<" | quit...join success"<<endl; } } cout<<"main thread join end..."<<endl; return 0; }
主线程对这五个线程进行了等待
下面再给线程设置退出码,为了方便查看这里设置成10,在主线程等待成功后打印线程的退出码。
#include<iostream> #include<unistd.h> #include<pthread.h> using namespace std; #define NUM 3 void* Routine(void* args) { int cnt=3; while(cnt){ cout<<"thread : "<<pthread_self()<<" | count : "<<cnt<<" | is running!"<<endl; cnt--; sleep(1); } return (void*)10; } int main() { pthread_t tids[NUM]; for(auto i=0;i<NUM;i++){ pthread_create(&tids[i],nullptr,Routine,nullptr); } cout<<"main thread join begin..."<<endl; for(auto i=0;i<NUM;i++){ void* ret = nullptr; pthread_join(tids[i],&ret) cout<<"thread "<<tids[i]<<" exitcode: "<<(int)ret<<" | quit...join success"<<endl; } cout<<"main thread join end..."<<endl; return 0; }
从运行结果可看出退出码改变为10
注意: pthread_join函数默认是以阻塞的方式进行线程等待的,而且pthread_join函数只能获取到线程正常退出时的退出码,用于判断线程的运行结果是否正确。
pthread_join函数不能获取到线程异常退出的退出码,因为线程是进程内的一个执行分支,如果进程中的某个线程崩溃了,那么整个进程也会因此而崩溃,此时我们没有机会执行pthread_join函数,因为整个进程已经退出了。
分离线程
- 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成内存泄漏。
- 但如果我们不关心线程的返回值,join也是一种负担,此时我们可以将该线程进行分离,后续当线程退出时就会自动释放线程资源。
pthread_detach
功能: 分离线程
函数原型:int pthread_detach(pthread_t thread);
参数: thread:被分离线程的ID。
返回值: 分离成功返回0,失败返回错误码。
线程分离可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离
创建一个线程让其自己分离
#include<iostream> #include<unistd.h> #include<pthread.h> using namespace std; void* Routinue(void* args) { pthread_detach(pthread_self());// 分离线程 int cnt=5; while(cnt){ cout<<"thread : "<<pthread_self()<<" cnt: "<<cnt--<<endl; sleep(1); } return (void*)10; } int main() { pthread_t tid; pthread_create(&tid,nullptr,Routinue,nullptr); sleep(1); int ret=pthread_join(tid,nullptr); cout<<"main thread ret: "<<ret<<endl; return 0; }
让主线程分离新线程#include<iostream> #include<unistd.h> #include<pthread.h> using namespace std; void* Routinue(void* args) { int cnt=5; while(cnt){ cout<<"thread : "<<pthread_self()<<" cnt: "<<cnt--<<endl; sleep(1); } return (void*)10; } int main() { pthread_t tid; pthread_create(&tid,nullptr,Routinue,nullptr); pthread_detach(tid);// 分离线程 sleep(1); int ret=pthread_join(tid,nullptr); cout<<"main thread ret: "<<ret<<endl; return 0; }
注意:
- 如果一个线程被设置为分离状态,该线程不应该被join
- 即使线程被设置为分离状态,线程出错崩溃仍会影响主线程和其他正常线程
线程ID及进程地址空间布局
- pthread_create函数会产生一个线程ID,存放在第一个参数指向的地址中,该线程ID和内核中的LWP不是一回事。
- 内核中的LWP属于进程调度的范畴,因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
- pthread_create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,这个ID属于NPTL线程库的范畴,线程库的后续操作就是根据该线程ID来操作线程的。
- 线程库NPTL提供的pthread_self函数,获取的线程ID和pthread_create函数第一个参数获取的线程ID是一样的。
pthread_t 到底是什么类型呢?
取决于实现。对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。
-
Linux多线程编程_linux_
2021-09-30 13:12:31linux多线程编程经验,非常不错的资料,可以学习 -
Linux多线程聊天课程设计.7z
2019-06-15 21:25:41Linux环境下多线程可视化聊天程序课程设计+源码 -
linux多线程编程(五)
2021-01-10 11:30:19线程 线程是计算机中独立运行的... 使用多线程的理由之一是和进程相比,它是一种非常“节俭”的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它 -
Linux多线程服务端编程 使用muduo C++网络库 PDF电子书下载 带目录书签 完整版
2018-06-13 18:56:58Linux多线程服务端编程 使用muduo C++网络库 PDF电子书下载 带目录书签 完整版 -
《Linux多线程服务端编程:使用muduo C++网络库》.(陈硕).[PDF]
2018-07-24 08:54:49《Linux多线程服务端编程:使用muduo C++网络库》.(陈硕).[PDF] -
Linux多线程编程手册_C++_linux网络编程
2021-09-11 00:48:14LINUX 多线程编程资料,主要讲解LINUX网络编程多线程这一块。 -
《Linux多线程服务端编程:使用muduo C++网络库》.(陈硕).[PDF]@ckook.pdf
2019-05-13 17:06:48《Linux多线程服务端编程:使用muduo C++网络库》pdf文档 -
《Linux多线程服务端编程:使用muduo C++网络库》.(陈硕).part3.rar
2017-09-20 09:11:22《Linux多线程服务端编程:使用muduo C++网络库》.(陈硕) 完整版并且带书签,对于网络编程有很好的参考价值。 -
linux多线程开发区别与window
2013-09-04 10:33:59Linux 平台上的多线程程序开发相对应其他平台(比如 Windows)的多线程 API 有一些细微和隐晦的差别,不注意这些 Linux 上的一些开发陷阱,常常会导致程序问题不穷,死锁不断。本文中我们从 5 个方面总结出 Linux 多... -
Linux多线程编程手册
2018-04-08 09:37:59Linux多线程编程手册,详述了Solaris 线程 API 和 pthread API