Linux线程与pthread库
在Linux系统中,线程控制是多任务编程的核心,而POSIX线程库(pthread库)则是应用层的原生线程库,为开发者提供了丰富的线程控制功能。
1. 线程和pthread库
1.1 线程创建
在Linux系统中,通过pthread库提供的pthread_create函数可以创建新的线程。该函数的原型如下:
参数:
thread:参数是一个输出型参数,用于获取创建成功的线程ID。
attr:参数用于设置线程的属性,传入NULL表示使用默认属性。
start_routine:参数是一个函数地址,表示线程启动后要执行的函数。
arg:参数是传给线程例程的参数。
1.2 线程ID获取
每个线程都有一个唯一的线程ID,通过 pthread_self 函数可以获取当前线程的ID。线程ID是一个无符号长整型数。
1.3. 线程等待
线程的等待是为了确保资源得到释放,避免系统泄漏。使用 pthread_join 函数可以等待线程的结束。
参数:
thread:参数是被等待线程的ID。
retval:存储线程的返回值(退出状态信息)的位置。
返回值:线程等待成功返回0,失败返回错误码。
下面的示例演示了线程的创建和等待过程:
在编译时,需要链接pthread库。在使用gcc编译时,可以通过在命令行中添加"-pthread"选项来链接pthread库。例如:
gcc -o my_program my_program.c -pthread
#include <pthread.h>
#include <stdio.h> // 添加了这个头文件以使用printf
#include <unistd.h>
#include <sys/types.h>
// 线程执行的工作
void* thread_routine(void* arg) {
char* msg = (char*)arg;
int n = 10;
pthread_t tid = pthread_self();
pid_t pid = getpid();
pid_t ppid = getppid();
// 在循环中输出线程信息
while (n--) {
// 修正了输出格式符
printf("线程消息:%s pid: %d, ppid:%d, tid: %lu\n", msg, pid, ppid, tid);
sleep(1);
}
// 返回线程的退出值
return ((void*)42);
}
int main() {
pthread_t tid; // 存放新线程的ID
const char* message = "你好,线程"; // 线程的消息
void* exit_code; // 用于存放线程的退出值
// 创建线程
if (pthread_create(&tid, NULL, thread_routine, (void*)message) != 0) {
printf("创建线程失败\n");
return 1;
}
// 输出主线程信息
printf("主线程消息:%s pid: %d, ppid:%d, tid: %lu\n", message, getpid(), getppid(), pthread_self());
// pthread_join 会阻塞等待子线程结束
if (pthread_join(tid, &exit_code) == 0) {
// 修正了输出格式符
printf("线程返回值:%ld\n", (long)exit_code);
} else {
printf("等待线程失败\n");
}
return 0; // 返回主函数的退出值
}
输出:
运行时使用 ps -aL 显示当前系统中所有线程的信息
线程ID(TID) 是在进程内唯一的,它用于在同一进程内区分不同的线程。
进程ID(PID) 用于在整个系统中唯一标识一个进程。
如果PID和LWP相同,表示这个标识是主线程的标识;
如果它们不同,那么通常PID表示整个进程,而LWP表示不同的线程。
2. 线程的终止与退出
线程的终止与退出有三种方式:
- 使用 return 语句:在线程函数中使用 return 语句可以使线程提前结束,但主线程使用 return 并不会终止整个进程。
示例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* thread_routine(void* arg) {
// 线程执行的工作
return (void*)42; // 返回退出码 42
}
int main() {
pthread_t tid;
if (pthread_create(&tid, NULL, thread_routine, NULL) != 0) {
printf("创建线程失败\n");
return 1;
}
// 等待线程退出,并获取退出码
void* exit_code;
pthread_join(tid, &exit_code);
printf("线程退出码:%ld\n", (long)exit_code);
return 0;
}
- 使用 pthread_exit 函数:允许线程独立终止,而不影响其他线程或整个进程
参数:
retval:线程退出时的退出码信息。功能:
pthread_exit 函数允许线程独立终止,而不影响其他线程或整个进程。线程可以通过调用 pthread_exit 提前结束自己的执行,并传递一个退出码。
示例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* thread_routine(void* arg) {
// 线程执行的工作
pthread_exit((void*)42); // 线程退出,返回退出码 42
}
int main() {
pthread_t tid;
if (pthread_create(&tid, NULL, thread_routine, NULL) != 0) {
printf("创建线程失败\n");
return 1;
}
// 等待线程退出,并获取退出码
void* exit_code;
pthread_join(tid, &exit_code);
printf("线程退出码:%ld\n", (long)exit_code);
return 0;
}
- 使用 pthread_cancel 函数:用于取消指定线程,被取消的线程可以在任何地方被取消,不仅限于线程函数的返回点。
参数:
thread:要取消的线程的线程ID。
功能:
pthread_cancel 函数用于请求取消指定线程。该函数向指定线程发送取消请求,被取消的线程可以在任何地方被取消,不仅限于线程函数的返回点。
示例:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
// 线程函数
void* thread_routine(void* arg) {
// 线程执行的工作
while(1) {
printf("Thread ID: %lu\n", pthread_self());
sleep(1);
}
}
int main() {
pthread_t tid;
// 创建线程
if (pthread_create(&tid, NULL, thread_routine, NULL) != 0) {
// 处理线程创建失败
return 1;
}
sleep(3);
// 取消线程
pthread_cancel(tid);
// 等待线程退出
pthread_join(tid, NULL);
return 0;
}
3. 为什么需要线程库?
在Linux内核中,线程的管理并不是直接由内核进行的,而是通过引入轻量级进程(Lightweight Process,LWP)的概念来实现。这种设计的核心思想是让内核专注于进程的管理,而将线程的管理交给用户空间的线程库。
轻量级进程(LWP): 在Linux内核中,每个进程都被分配一个LWP,它实际上是内核对进程的抽象,具有独立的PID,但共享相同的资源。LWP拥有独立的执行流,但线程的管理由用户空间的线程库负责。
线程概念在用户空间: Linux中没有专门的系统调用用于线程管理,而是通过用户空间的线程库(如pthread库)提供的接口实现。线程库中的函数用于创建、销毁和管理线程,这些线程在内核中对应LWP。
内核分工与线程库作用: 内核不直接管理线程,而是将管理任务委托给用户空间的线程库。线程库负责调度、切换和同步线程,提供高层次的线程抽象,使开发者能更轻松地进行多线程编程。
用户空间线程库的作用: 线程库在实现线程管理时可能使用内核提供的机制,但它们是用户空间的实体。线程库简化了开发者对多线程的操作,让他们不必深入了解内核的底层细节。
4. 虚拟地址空间与线程库
-
线程库在虚拟地址空间中位于共享区域。具体来说,用户空间的线程库,例如pthread库,通常会被加载到共享区域,这使得它对于同一进程内的所有线程都是可见的。在这个共享区域内,每个新线程都有一个对应的线程控制块(Thread Control Block,TCB),用于存储关于线程的各种信息,确保线程的独立性。
-
每个新线程在共享区域都有一个对应的线程控制块(Thread Control Block,TCB)。线程控制块存储了关于线程的各种信息,确保了线程的独立性。在pthread库中,线程ID(tid)指向了对应线程控制块的起始地址。通过这种方式,用户空间的线程库可以有效地管理和操作线程,而不必依赖内核级别的线程管理。线程控制块的存在使得线程库能够追踪线程的状态、优先级、寄存器值等信息,从而更好地协调和调度线程的执行。这种设计使得用户空间的线程库能够更加灵活地处理线程的创建、调度和撤销等操作,降低了对内核的依赖性。