一个网站两个域名,wordpress目录分类设置,永州网站制作,网站建设属于广告费吗阅读导航 引言一、 Linux线程概念1. 什么是线程2. 线程的概念3. 线程与进程的区别4. 线程异常 二、Linux线程控制1. POSIX线程库2. 创建线程 pthread_create() 函数#xff08;1#xff09;头文件#xff08;2#xff09;函数原型#xff08;3#xff09;参数解释#x… 阅读导航 引言一、 Linux线程概念1. 什么是线程2. 线程的概念3. 线程与进程的区别4. 线程异常 二、Linux线程控制1. POSIX线程库2. 创建线程 pthread_create() 函数1头文件2函数原型3参数解释4返回值5使用示例 3. 线程ID及进程地址空间布局1进程地址空间布局2线程ID pthread_self() 函数 4. 线程等待 pthread_join() 函数1头文件2函数原型3参数解释4返回值5使用示例 5. 线程终止1线程终止的三种方法2pthread_exit() 函数3pthread_cancel() 函数 三、分离线程1. joinable与线程分离2. 分离线程 pthread_detach() 函数1头文件2函数原型3参数解释4返回值5使用示例 四、线程的优缺点五、线程用途温馨提示 引言
在当今信息技术日新月异的时代多线程编程已经成为了日常开发中不可或缺的一部分。Linux作为一种广泛应用的操作系统其对多线程编程的支持也相当完善。本文将会介绍关于Linux多线程相关的知识其中包括了线程的概念、线程控制、线程分离等方面的内容。如果你希望提升自己的多线程编程能力本文将为你提供实用的技术指导和详尽的知识储备。让我们一起来深入了解Linux多线程编程的奥秘吧
一、 Linux线程概念
1. 什么是线程
在一个程序里的一个执行路线就叫做线程thread。更准确的定义是线程是“一个进程内部的控制序列”。一切进程至少都有一个执行线程。线程在进程内部运行本质是在进程地址空间内运行。在Linux系统中在CPU眼中看到的PCB都要比传统的进程更加轻量化。透过进程虚拟地址空间可以看到进程的大部分资源将进程资源合理分配给每个执行流就形成了线程执行流。
2. 线程的概念
在Linux中线程是指在同一个进程内部并发执行的多个子任务。Linux将线程作为轻量级进程LWP来实现每个线程共享相同的进程地址空间和其他资源包括文件描述符、信号处理器和其他内核状态。由于线程之间共享进程的资源因此线程之间的切换开销通常比进程之间的切换要小得多。
Linux使用POSIX线程库pthread来支持多线程编程。通过pthread库开发人员可以方便地创建、控制和同步线程实现多线程编程的各种功能。在Linux中线程的创建和管理都是通过系统调用和pthread库来完成的开发人员可以使用pthread_create()函数创建新线程并使用pthread_join()函数等来等待线程的结束后面我们会详细介绍。
在Linux中线程与进程一样拥有自己的ID、寄存器上下文、栈和线程本地存储TLS。线程可以通过共享内存进行通信也可以使用线程同步机制来协调彼此的操作。在多核处理器上Linux内核会将不同的线程分配到不同的处理器核心上并行执行以提高系统的性能和响应速度。
总的来说Linux线程是在同一个进程内并发执行的多个子任务通过共享进程的资源和使用pthread库来实现线程的创建和管理。在Linux环境下充分利用线程可以提高程序的并发能力和性能表现。
3. 线程与进程的区别
Linux线程和进程的主要区别在于它们是操作系统对应不同的执行单元。 资源分配 进程是资源分配的最小单位每个进程都有自己的地址空间、全局变量、堆栈、文件描述符等系统资源。线程是CPU调度的最小单位多个线程可以共享同一个进程的资源包括地址空间、全局变量、文件描述符等。 切换开销 因为线程共享进程资源因此线程之间的切换开销比进程之间的切换要小得多。线程的上下文切换只需要保存处理器寄存器和栈指针而进程切换需要保存整个进程的上下文信息包括内存映像、堆栈、寄存器等。 通信机制 进程之间通常使用IPCInter-Process Communication机制来进行进程间通信例如管道、消息队列、共享内存和信号量等。线程之间可以通过共享内存、互斥锁、条件变量等同步机制来进行通信和协调。 并发能力 由于线程共享进程资源和较小的切换开销因此线程可以更轻松地实现并发执行提高程序的并发处理能力和性能表现。 安全性 线程共享进程资源因此线程之间操作共享数据可能会引起竞态条件等并发问题需要使用同步机制来协调线程的操作。进程之间不共享地址空间可以通过IPC机制来实现安全的进程间通信。
⭕进程和线程的关系如下图
4. 线程异常 线程死锁线程之间相互等待对方释放资源导致所有线程都无法继续执行的情况。 竞态条件多个线程同时访问共享的资源导致意外的结果或者数据损坏。 内存泄漏线程未正确释放动态分配的内存导致系统资源耗尽。 线程间通信问题线程之间的通信出现问题例如数据丢失、阻塞等情况。 未捕获的异常线程中的代码抛出未捕获的异常导致线程意外终止。
⭕当一个线程出现严重的异常导致崩溃时会触发进程内的异常处理机制。在大多数操作系统中异常会导致信号被发送给进程例如SIGSEGV段错误或SIGFPE浮点异常。默认情况下这些信号会终止整个进程。
注意线程是进程的执行分支线程出异常就类似进程出异常进而触发信号机制终止进程进程终止该进程内的所有线程也就随即退出。
二、Linux线程控制
1. POSIX线程库
在Linux系统中POSIX线程库也称为pthread库是一套用于多线程编程的标准接口。它基于POSIX标准Portable Operating System Interface定义了一组函数和数据类型使得开发者可以方便地进行多线程程序的开发。
POSIX线程库的设计目标是提供一个可移植、高效和可靠的多线程编程接口。它已经成为类UNIX系统上标准的多线程编程接口并在Linux系统中得到广泛应用。开发者可以使用POSIX线程库编写具有良好可移植性的多线程程序无需关心底层操作系统的差异。
2. 创建线程 pthread_create() 函数
在Linux系统中线程的创建是通过POSIX线程库pthread库提供的函数来实现的
1头文件
pthread_create() 函数的使用需要包含pthread库的头文件pthread.h
#include pthread.h2函数原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);3参数解释 thread指向 pthread_t 类型的指针用于存储新线程的标识符。在函数成功返回时该指针被设置为新线程的标识符可以用于后续操作。 attr指向 pthread_attr_t 类型的指针用于设置线程的属性通常可以传入 nullptr表示使用默认属性。如果需要设置线程的属性可以使用 pthread_attr_init() 函数初始化属性对象并使用 pthread_attr_setxxx() 函数设置属性。 start_routine指向线程函数的指针即新线程的执行体。该函数应该以 void* func(void*) 的形式定义即带有一个 void 类型指针参数返回一个 void 类型指针。线程函数的返回值将作为线程的退出状态可以通过 pthread_join() 函数获取。 arg传递给线程函数的参数它必须是一个 void 类型指针开发者需要自行处理类型转换。
4返回值
如果成功创建了新线程则函数返回 0如果失败则返回一个非零值表示出现了错误。在出错的情况下可以使用 perror() 函数输出错误信息也可以使用 strerror() 函数获取错误信息。
5使用示例
#include stdio.h
#include pthread.hvoid* thread_function(void* arg) {int tid *(int*)arg;printf(This is thread %d.\n, tid);pthread_exit(NULL);
}int main() {pthread_t tid[3];int i, rc;// 创建三个新线程for (i 0; i 3; i) {rc pthread_create(tid[i], NULL, thread_function, (void*)i);if (rc ! 0) {fprintf(stderr, Failed to create new thread.\n);return 1;}}// 等待所有新线程结束for (i 0; i 3; i) {rc pthread_join(tid[i], NULL);if (rc ! 0) {fprintf(stderr, Failed to join the thread.\n);return 1;}}printf(All threads exit.\n);return 0;
}通过上面的步骤可以在 Linux 系统下成功创建并执行新的线程。需要注意的是调用 pthread_create() 函数时传递给线程函数的参数必须是指向整型变量的指针否则可能会出现不可预期的错误。 3. 线程ID及进程地址空间布局
1进程地址空间布局
进程地址空间布局是指操作系统在内存中为每个进程分配的地址空间的布局方式。以下是典型的Linux进程地址空间布局 代码段Text Segment 代码段存储了可执行程序的机器指令。它通常是只读的并且在内存中只有一份用于所有执行该程序的进程。 数据段Data Segment 数据段存储了全局变量和静态变量。它包括了初始化的数据和非初始化的BSS段Block Started by Symbol。数据段通常是可读写的。 堆Heap 堆是动态分配内存的区域。在运行时通过调用malloc()、calloc()等函数分配堆内存。堆的大小不固定可以根据需要动态增长或缩小。 栈Stack 栈用于存储函数调用、局部变量和函数参数等信息。每个线程都有自己的栈用于保存线程特定的上下文信息。栈的大小通常是固定的。 共享库Shared Libraries 共享库存储了被多个进程共享的代码和数据。它们被加载到内存中并映射到每个进程的地址空间中。 内核空间Kernel Space 内核空间是由操作系统内核使用的内存区域不属于进程的地址空间。它包括操作系统内核的代码和数据结构。 2线程ID pthread_self() 函数
线程IDThread ID是操作系统分配给每个线程的唯一标识符。在不同的操作系统中线程ID的表示方式和取值范围可能会有所不同。 pthread_self()函数是一个POSIX线程库中的函数用于获取当前线程的线程ID。它的原型如下
pthread_t pthread_self(void);该函数没有参数返回类型为pthread_t即线程ID的类型。
使用pthread_self()函数可以在多线程程序中获取当前线程的线程ID。每个线程在创建时都会被分配一个唯一的线程ID可以通过该ID来标识和区分不同的线程。
下面是一个简单的示例代码演示了如何使用pthread_self()函数获取当前线程的线程ID
#include stdio.h
#include pthread.hvoid* thread_func(void* arg) {pthread_t tid pthread_self();printf(Thread ID: %lu\n, tid);return NULL;
}int main() {pthread_t tid;pthread_create(tid, NULL, thread_func, NULL);pthread_join(tid, NULL);return 0;
}在上述示例中主线程创建了一个新线程并通过pthread_create()函数启动线程执行thread_func()函数。在thread_func()函数中调用pthread_self()函数获取当前线程的线程ID并将其打印输出。
注意线程ID的类型pthread_t可能是一个不透明的数据类型具体实现取决于操作系统和编译器。在上述示例中使用%lu格式指定符打印无符号长整型以与pthread_t类型匹配。在不同的系统和编译环境中可能需要根据具体情况调整打印格式。
4. 线程等待 pthread_join() 函数
⭕线程等待是一种同步机制会导致线程之间的阻塞和等待。在设计多线程程序时需要合理地安排线程的执行顺序和等待关系以避免死锁、饥饿等问题。pthread_join()函数是一个POSIX线程库中的函数用于等待指定的线程结束并回收其资源。
1头文件
pthread_join() 函数的使用需要包含pthread库的头文件pthread.h
#include pthread.h2函数原型
int pthread_join(pthread_t thread, void **retval);3参数解释
thread参数是要等待的目标线程的线程IDretval参数用于接收目标线程的返回值如果有。pthread_join()函数会阻塞调用线程直到目标线程结束为止并且可以获取目标线程的返回值。
4返回值
pthread_join() 函数的返回值表示线程的终止状态具体取值如下
如果线程的返回值已经被存放到 value_ptr 指向的内存中则返回 0。如果指定的线程在执行过程中被取消则返回 PTHREAD_CANCELED。如果调用该函数时出现错误则返回相应的错误代码。
5使用示例
下面是一个简单的示例代码演示了如何使用pthread_join()函数等待子线程结束并获取其返回值
#include stdio.h
#include stdlib.h
#include pthread.hvoid* thread_func(void* arg) {int* result (int*)malloc(sizeof(int));*result 42;printf(Thread is about to exit\n);pthread_exit((void*)result); // 终止线程并返回 result
}int main() {pthread_t tid;void* ret_val;pthread_create(tid, NULL, thread_func, NULL);pthread_join(tid, ret_val); // 获取线程的返回值if (ret_val) {printf(Thread returned: %d\n, *((int*)ret_val));free(ret_val); // 释放返回值对应的内存} else {printf(Thread returned NULL\n);}return 0;
}在上面的示例中我们创建了一个新线程线程函数中使用了 pthread_exit() 函数来结束线程并返回一个整数值。在主线程中我们通过 pthread_join() 函数等待线程的终止并获取了线程的返回值。
注意pthread_join()函数会使调用线程进入阻塞状态直到目标线程结束。如果不关心目标线程的返回值也可以将retval参数设置为NULL。另外在多线程程序中需要特别注意线程的安全退出和资源回收以避免产生悬挂线程或资源泄漏的问题。
5. 线程终止
1线程终止的三种方法 线程函数返回线程函数执行完毕并从函数中返回线程会自动终止。线程函数可以通过返回一个值来传递结果给线程的创建者。 调用 pthread_exit() 函数线程可以显式地调用 pthread_exit() 函数来终止自己。这个函数接受一个参数作为线程的返回值可以被其他线程通过调用 pthread_join() 函数获取。 取消线程线程可以被其他线程取消。调用 pthread_cancel(pthread_t thread) 函数可以请求取消指定的线程。被取消的线程可以选择在适当的时机终止自己或者忽略取消请求继续执行。
2pthread_exit() 函数
pthread_exit() 函数是用于终止当前线程并返回一个值的 POSIX 线程库函数。该函数的原型如下所示
void pthread_exit(void* value_ptr);value_ptr表示线程的返回值可以是任意类型的指针。当线程调用 pthread_exit() 函数时会将 value_ptr 指向的内容作为线程的返回值。
pthread_exit() 函数允许线程在执行过程中随时退出并返回一个值。这个返回值可以被其他线程通过调用 pthread_join() 函数获取从而实现线程间的数据交互和结果传递。
下面是一个简单的示例演示了如何在线程中使用 pthread_exit() 函数来结束线程并返回一个值
#include stdio.h
#include stdlib.h
#include pthread.hvoid* thread_func(void* arg) {int* result (int*)malloc(sizeof(int));*result 42;printf(Thread is about to exit\n);pthread_exit((void*)result); // 终止线程并返回 result
}int main() {pthread_t tid;void* ret_val;pthread_create(tid, NULL, thread_func, NULL);pthread_join(tid, ret_val); // 获取线程的返回值printf(Thread returned: %d\n, *((int*)ret_val));free(ret_val); // 释放返回值对应的内存return 0;
}在上面的示例中我们创建了一个新线程线程函数中使用了 pthread_exit() 函数来结束线程并返回一个整数值。在主线程中我们通过 pthread_join() 函数获取了线程的返回值并打印输出了该值。
3pthread_cancel() 函数
pthread_cancel() 函数是 POSIX 线程库中用于取消指定线程的函数。该函数的原型如下所示
int pthread_cancel(pthread_t thread);thread表示要取消的线程的标识符。
pthread_cancel() 函数会向指定线程发送一个取消请求并尝试终止该线程的执行。但是线程是否会被成功取消取决于多个因素包括线程自身的取消状态和取消点的设置。
pthread_cancel() 函数只是发送一个取消请求并立即返回并不能保证目标线程会立即终止。目标线程可以选择忽略取消请求或者在适当的取消点进行处理。
取消点cancellation point是线程中的一些特定位置线程在这些位置上可以检查是否有取消请求并决定是否终止自己的执行。常见的取消点包括 I/O 操作、阻塞的系统调用等。
如果目标线程成功响应了取消请求并在取消点终止了执行那么取消状态将被设置为已取消。可以通过调用 pthread_setcancelstate() 和 pthread_setcanceltype() 函数来控制线程的取消状态和取消类型。
下面是一个示例演示了如何使用 pthread_cancel() 函数来取消线程的执行
#include stdio.h
#include pthread.hvoid* thread_func(void* arg) {while (1) {// 线程执行的逻辑}return NULL;
}int main() {pthread_t tid;pthread_create(tid, NULL, thread_func, NULL);// 在主线程中取消子线程的执行pthread_cancel(tid);pthread_join(tid, NULL); // 等待线程结束return 0;
}在上面的示例中我们创建了一个新线程并在主线程中调用 pthread_cancel() 函数来取消子线程的执行。接着我们使用 pthread_join() 函数等待子线程的终止。
注意在使用 pthread_cancel() 函数取消线程时应确保目标线程处于可取消状态并且在适当的位置设置了取消点。否则取消请求可能被忽略导致线程无法正确终止。
三、分离线程
1. joinable与线程分离
“joinable” 和线程分离是两种不同的线程状态。
“joinable” 状态的线程是指可以被其他线程显式等待和回收资源的线程。在 POSIX 线程库中默认情况下创建的线程是可连接状态joinable。可连接状态的线程需要使用 pthread_join() 函数来等待其终止并获取其返回值如果有。线程分离是指将线程属性设置为分离状态使得线程在终止时可以自动释放相关资源而无需等待其他线程显式对其进行回收。分离线程通常用于不需要获取线程返回值或进行线程同步的场景。
2. 分离线程 pthread_detach() 函数
pthread_detach() 函数是 POSIX 线程库中的一个函数用于将指定线程设置为分离状态即使该线程在终止时可以自动释放相关资源而无需等待其他线程显式对其进行回收。
1头文件
pthread_detach() 函数的使用需要包含pthread库的头文件pthread.h
#include pthread.h2函数原型
int pthread_detach(pthread_t thread);3参数解释
thread表示要设置为分离状态的线程的标识符。
4返回值
pthread_detach() 函数的返回值为 0 表示调用成功返回值为非零表示调用失败。失败的原因可能是参数不正确或者内部出现了错误。
注意线程在被设置为分离状态之前必须处于可连接状态。否则pthread_detach() 函数将无法将其设置为分离状态并返回一个错误码。
5使用示例
下面是一个示例演示了如何使用 pthread_detach() 函数将线程设置为分离状态
#include stdio.h
#include pthread.hvoid* thread_func(void* arg) {// 线程执行的逻辑return NULL;
}int main() {pthread_t tid;pthread_create(tid, NULL, thread_func, NULL);// 将线程设置为分离状态pthread_detach(tid);// 不需要使用 pthread_join() 函数进行回收return 0;
}在上面的示例中我们创建了一个新线程并使用 pthread_detach() 函数将其设置为分离状态。由于该线程已经处于分离状态因此在主线程中无需使用 pthread_join() 函数进行回收。
四、线程的优缺点
线程是一种轻量级的执行单元可以在一个进程内并发执行多个任务。线程有以下优点和缺点
✅优点
并发执行线程允许多个任务同时执行提高了程序的运行效率和响应速度。可以充分利用多核处理器的计算能力。共享数据线程可以共享同一个进程的内存空间方便数据之间的共享和通信。不同线程之间可以直接读取和修改同一块内存区域的数据简化了多任务编程的复杂性。资源高效线程的创建和销毁消耗的资源相对较少线程切换的开销也较小。相比于进程线程更加轻量级。逻辑清晰使用线程可以将程序分解成多个独立的执行单元每个线程负责不同的任务使得程序的逻辑结构更加清晰、模块化。
✅缺点
同步和共享问题多个线程访问共享数据时需要进行同步操作以避免数据竞争和不一致的结果。需要使用锁、信号量等机制来保护共享资源增加了编程的复杂性。错误管理困难线程共享同一进程的内存空间一个线程对共享资源的错误操作可能会影响其他线程的正常执行导致难以追踪和调试错误。调试和测试复杂由于线程并发执行线程之间的交互和调试相对复杂。当程序出现问题时需要仔细分析各个线程的执行顺序和交互情况增加了调试和测试的难度。资源竞争多个线程同时访问共享资源时可能引发资源竞争问题如死锁、饥饿等。需要合理设计和管理线程的同步和互斥机制以避免资源竞争问题。
五、线程用途
合理的使用多线程能提高CPU密集型程序的执行效率。合理的使用多线程能提高IO密集型程序的用户体验。如生活中我们一边写代码一边下载开发工具就是多线程运行的一种表现
温馨提示
感谢您对博主文章的关注与支持如果您喜欢这篇文章可以点赞、评论和分享给您的同学这将对我提供巨大的鼓励和支持。另外我计划在未来的更新中持续探讨与本文相关的内容。我会为您带来更多关于Linux以及C编程技术问题的深入解析、应用案例和趣味玩法等。如果感兴趣的话可以关注博主的更新不要错过任何精彩内容
再次感谢您的支持和关注。我们期待与您建立更紧密的互动共同探索Linux、C、算法和编程的奥秘。祝您生活愉快排便顺畅