目录
前言:
一,线程退出
二,进程异常
三,资源共享
四,单线程的代码运用
五,多线程的代码运用
六,线程的优缺点
七,线程的私有与共享
前言:
学习有关线程的代码运用需要了解线程的注意点以及原生线程库中常用的函数。下面来一一说明。
一,线程退出
线程的退出首先需注意:主线程退出 == 进程退出 == 所有线程都要退出,因此,往往我们需要main thread最后结束。其次,线程与进程一样占用系统资源,也需要被等待回收,否则会产生类似进程那里内存泄漏的问题,但这里不能使用进程中类似于exit直接终止的函数,因为线程之间资源共享,一旦其中一个线程被类似管理进程中的信号所杀掉,所有的线程将直接被信号杀掉,也就无法捕捉信号。线程中使用pthread_exit
或pthread_cancel
来退出指定的线程。
二,进程异常
单个线程如果出现异常问题与进程一样,整个程序会直接崩溃。多线程中的异常与进程不同。进程之间相互独立,互不影响,所以进程中的异常情况通常由父进程考虑,线程与进程不同,如果任何一个线程出现异常(比如:除0,野指针等),都会导致整个进程退出!因为线程是进程的执行分支,它们之间共享资源,关联性很强。线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程内的所有线程也就随即退出,即健壮性低。
三,资源共享
线程之间资源共享,不同的线程分别执行不同的函数代码区域(多线程对应的进程地址空间中资源共享,其中代码区划分了几段,不同线程分别对应不同区域),但整体代码是共享的。这里可通过全局变量测试出。
四,单线程的代码运用
首先,这里需要了解Linux系统原生线程库中的常用函数。
1,pthread_self
函数是原生线程库(pthread库)中的一个函数,用于获取当前线程的线程标识符。这个标识符在进程的所有线程中是唯一的,可以用来区分不同的线程。
头文件:
#include <pthread.h>
格式:
pthread_t pthread_self(void);
参数说明:
pthread_t
:一个用于存储线程标识符的不透明的数据类型。注意,线程标识符对于每个线程在其生命周期内是唯一的,一旦线程终止,其标识符可能会被重用。
2,pthread_join
函数是原生线程库中的一个重要函数,用于等待指定的线程终止。当你创建一个线程后,主线程(或任何其他线程)可能会需要等待这个新创建的线程完成其执行,避免产生类似进程那里内存泄漏的问题。
头文件:
#include <pthread.h>
相关说明:
该函数提供了一种线程同步的机制。如果指定的线程没有退出,调用它的线程将阻塞等待,直到指定的线程结束。
格式:
int pthread_join(pthread_t thread, void **retval);参数说明:
thread:这是你想要等待的线程的标识符(ID),即你想要连接的线程。
retval:这是一个指向
void
指针的指针,用于接收被等待线程的返回值。如果不关心线程的返回值,可以将此参数设置为NULL
。
pthread_create
函数创建新线程后,新线程执行代码段中划分的指定区域,即执行专门的函数。该函数的参数和返回值都是viod*,retval接收的参数就是该函数返回值的指针。返回值:
成功时,
pthread_join
返回0
;失败时,返回错误码。常见的错误码包括EINVAL
(表示传入的线程 ID 无效)和ESRCH
(表示没有这样的线程 ID)。注意:
pthread_join不考虑线程异常情况,因为一个线程异常将会导致整个程序被杀掉,这里无法像进程一样获取线程的退出信号。
3,pthread_exit
是原生线程库中用于线程终止的函数。当一个线程调用 pthread_exit
时,它会结束其执行,并释放与线程相关的资源。
头文件:
#include <pthread.h>
格式:
void pthread_exit(void *retval);参数:
retval
:指向线程返回对象的指针。如果线程不需要返回任何值,可以将此参数设置为NULL
。注意:
线程在终止时返回一个指向返回对象的指针,其他线程可以通过
pthread_join
函数来获取这个返回值。
代码演示:
#include <iostream>
#include <pthread.h>
using namespace std;
int result = 10;
void* thread_function(void* arg)
{
//result是线程的返回结果
//注意:不能使用局部变量,因为函数执行完毕时,这个局部变量(及其存储的内存)通常会被释放或回收,此时输出的返回值是一个随机值
pthread_exit((void*)&result);
}
int main()
{
pthread_t thread;
void* ret = nullptr;
pthread_create(&thread, NULL, thread_function, NULL);
//获取线程的返回值——类型void*
pthread_join(thread, &ret);
//注意,这里需要强制转换为指定的类型
cout << "Thread returned: " << *(int*)ret << endl;
return 0;
}
4,pthread_cancel
是原生线程库中用于请求取消指定线程的函数,但它不会释放指定线程的资源。以下是对 pthread_cancel
的详细说明:
头文件:
#include <pthread.h>
格式:
int pthread_cancel(pthread_t thread);
参数:
thread
:指定取消的线程的标识符。返回值:
成功时返回 0。失败时,返回一个非零的错误码。
注意:
pthread_cancel通常与
pthread_join
连用。该函数可能导致资源泄漏、死锁或数据不一致等问题,因此,在取消线程之前,最好使用pthread_join
函数使之能够安全地释放它所占用的资源。
单线程综合代码运用请观看此链接:Linux单线程代码运用 。运行图如下:
五,多线程的代码运用
当一个程序中依次创建多个线程时需注意一点,多线程之间往往不可使用同一个变量,因为主线程与新建线程之间是并发运行,这就可能导致其中一个线程正在被CUP处理器处理时,主线程已经创建了多个线程,多线程共同运行处理同一个数据时就可能导致数据不一致问题。
再来说明下线程传参和返回值。线程传参和返回值都是void*型,这也就意味着我们即可以传递级别信息,也可以传递其他对象(包括自定义对象)。
多线程综合代码运用请观看此链接:Linux多线程代码运用 。运行图如下:
六,线程的优缺点
进程:进程是程序的实例,是资源分配的基本单位。它具有独立的地址空间、代码、数据和系统资源。因此,无论是创建进程还是在进程切换时,操作系统需要创建或保存及恢复大量的上下文信息,包括但不限于地址空间(涉及页表刷新)、文件描述符、寄存器状态等,涉及更多资源和数据的相关操作。这些流程不仅带来了较大的资源开销,其速度运行还相对较慢。
线程:线程是进程内的执行单元,是调度的基本单位。多个线程共享同一个进程的地址空间和系统资源。因此,在线程的创建或切换时,操作系统只需创建或保存及恢复线程的上下文信息,如寄存器状态、程序计数器等,而无需涉及地址空间等复杂资源的切换,涉及的资源较少,使得线程切换的资源开销相对较小,速度较快。
由于线程的这种性质,所以线程具有以下优缺点:
线程的优点:
1,创建一个新线程的代价要比创建一个新进程小得多。线程占用的资源要比进程少很多。
2,线程与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多。
3,线程能充分利用多处理器的可并行数量,在计算密集型应用,能在多处理器系统上运行,将计算分解到多个线程中实现,这样一来还可以使其在等待慢速I/O操作结束的同时,程序可执行其他的计算任务。
线程的缺点:
线程健壮性降低。多线程之间资源共享,编写多线程需要更全面更深入的考虑,在一个多线程程序里,因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
七,线程的私有与共享
线程的私有数据:
1,线程ID。每个线程都有自己专属的ID。
2,一组寄存器和线程的硬件上下文。线程要被CUP单独调度,是一个单独的执行流,这就决定了必须要有自己的寄存器,而CPU寄存器的值(硬件上下文),反映了当前进程或线程的运行状态,这是线程独有的数据。
3,线程的独立栈结构。每个线程要执行对应的函数代码就决定了必须要有自己独立的栈结构,这是线程的常规运行。
线程的共享数据:
1,代码和全局数据。线程共享进程地址空间中的资源,其中共享代码区中的代码及全局数据。比如,如果定义一个函数,那么该函数会在各线程中调用,即函数被重入;如果定义一个全局变,那么该变量也会在各线程中被访问到。
2,进程文件描述符表。一个进程可包含多个线程,这些线程共享进程的文件描述符表。
3,每种信号的处理方式。进程中的任意一个线程出了问题将会使整个进程被指定信号所杀,导致整个程序崩溃,这也就说明了进程中各个信号的处理方式和自定义信号的处理函数都在线程中通用
注意:由于线程共享资源的特性,这将会导致在多线程环境中,如果一个线程要是出问题了,那么其它线程也出问题,使整个进程退出,即线程安全问题。