目录
一、什么是线程
二、线程和进程的区别
三、线程的操作
1、创建线程
2、获取线程ID
3、线程的终止与等待
4、线程分离
一、什么是线程
在Linux中,线程(thread)是一种轻量级进程(Light-weight Process, LWP)的概念,它是进程内部的一个执行流,代表了程序中的一个独立执行路径。
每个线程都拥有自己的程序计数器(PC)、栈、寄存器集合以及错误返回码(errno),但同时,线程之间共享所属进程的地址空间、文件描述符、信号处理器以及其它资源。这意味着线程可以在同一地址空间内并发执行不同的任务,有效地利用多核处理器的能力,并且线程间的通信开销比进程间通信要小得多。
从内核角度来看,线程和进程在Linux中并没有本质区别,它们都被视为任务,并由内核调度。每个线程和进程都有自己的进程控制块(PCB),在Linux中称为task_struct,但线程之间的PCB在某些方面(如内存空间)是共享的。线程的创建通常通过clone()
系统调用来实现,通过传递特定的标志来决定新创建的实体与父进程之间资源共享的程度。
线程具有两个特点:
1、轻量化:创建线程更简单,因为不需要申请资源,与进程共用。
2、线程在进程的地址空间中运行。
示意图:
OS如果支持线程,那么也必须管理,之前我们知道管理进程的结构PCB,但是如果管理线程再实现一个虽然可以,但是没有必要,在Linux中的实现方法是统一视为轻量级进程,这样实现更为简单。
二、线程和进程的区别
进程=内核数据结构+代码和数据
进程时承担系统资源的基本实体,线程是CPU调度的基本单位。而进程是操作系统调度的基本单位。
进程是资源分配的最小单位。每个进程都有独立的地址空间、内存、文件描述符集、打开的文件和其他资源。进程之间是隔离的,一个进程的崩溃通常不会直接影响其他进程。
线程共享其所属进程的资源,包括
文件描述符表每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)当前工作目录用户id和组id线程不直接拥有系统资源,但可以访问其所在进程的所有资源。
每个线程有自己的
线程ID一组寄存器栈errno信号屏蔽字调度优先级
进程和线程的关系图:
三、线程的操作
POSIX线程库:
与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以 “pthread_” 打头的要使用这些函数库,要通过引入头文 <pthread.h>链接这些线程函数库时要使用编译器命令的“-lpthread”选项
1、创建线程
使用pthread_create()
函数创建新线程。这个函数需要四个参数:指向线程标识符的指针、线程属性(通常为NULL使用默认属性)、线程入口函数的地址以及传递给线程入口函数的参数。
功能:创建一个新的线程原型int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);参数thread: 返回线程 IDattr: 设置线程的属性, attr 为 NULL 表示使用默认属性start_routine: 是个函数地址,线程启动后要执行的函数arg: 传给线程启动函数的参数返回值:成功返回 0 ;失败返回错误码
代码示例:
#include <pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <unistd.h>
void* thread_function(void* arg);
int main() {
pthread_t thread_id;
int rc = pthread_create(&thread_id, NULL, thread_function, NULL);
if (rc) {
perror("pthread_create");
exit(EXIT_FAILURE);
}
// 主线程继续执行其他任务
while(1)
{
printf("i am running,pid:%d\n",getpid());
sleep(1);
}
return 0;
}
void* thread_function(void* arg) {
while(1)
sleep(1);
// 线程执行的代码
return NULL;
}
可以看到LWP就是线程ID,而PID=LWD的就是主线程,Linux将它们都视为轻量级进程。
2、获取线程ID
#include <iostream>
#include <string>
#include <functional>
#include <vector>
#include <time.h>
#include <unistd.h>
#include <pthread.h>
const int threadnum = 5;
// 新线程
void *ThreadRountine(void *args)
{
size_t num=(size_t)args;
while (true)
{
std::cout<<"i am thread"<<num<<"thread ID"<<pthread_self()<<std::endl;
sleep(2);
}
}
int main()
{
std::vector<pthread_t> pthreads;
for (size_t i = 0; i < threadnum; i++)
{
char threadname[64];
snprintf(threadname, sizeof(threadname), "%s-%lu", "thread", i);
pthread_t tid;
pthread_create(&tid, nullptr, ThreadRountine, (void*)i);
pthreads.push_back(tid);
sleep(1);
}
std::cout << "thread id: ";
for(const auto &tid: pthreads)
{
std::cout << tid << std::endl;
}
std::cout << std::endl;
while (true)
{
std::cout << "main thread" << std::endl;
sleep(3);
}
}
3、线程的终止与等待
功能:线程终止 原型 void pthread_exit(void *value_ptr); 参数 value_ptr:value_ptr不要指向一个局部变量。如果不需要传递退出状态,可以传递NULL. 返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
功能:取消一个执行中的线程
原型
int pthread_cancel(pthread_t thread);
参数
thread:线程ID
返回值:成功返回0;失败返回错误码
pthread_join函数
功能:等待线程结束
原型
int pthread_join(pthread_t thread, void **value_ptr);
参数
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码
我们使用这段代码来测试:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
// 线程执行的函数
void* thread_function(void* arg) {
printf("Thread start working...\n");
sleep(5); // 模拟工作一段时间
// 使用pthread_exit退出线程并传递一个整数作为退出状态
pthread_exit((void*)100); // 传递100作为退出状态
}
int main() {
pthread_t thread_id; // 线程ID
int* exit_status=NULL; // 用于存储线程退出状态的指针
// 创建线程
if(pthread_create(&thread_id, NULL, thread_function, NULL) != 0) {
perror("pthread_create");
exit(EXIT_FAILURE);
}
printf("Main thread continues doing other tasks...\n");
// 等待线程结束并获取退出状态
if(pthread_join(thread_id, (void**)&exit_status) != 0) {
perror("pthread_join");
exit(EXIT_FAILURE);
}
printf("Thread exited with status: %p\n", exit_status);
return 0;
}
最后等待到进程:
当然,退出的返回值可以定义成任何东西,我们得到的应该是void*
要注意void不能定义变量,但是void*可以,因为void*本质是地址。
4、线程分离
int pthread_detach(pthread_t thread);
//可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:
pthread_detach(pthread_self());
代码示例:
我们在线程调用函数中分离这个线程:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
// 线程执行的函数
void* thread_function(void* arg) {
printf("Thread start working...\n");
sleep(1); // 模拟工作一段时间
// 使用pthread_exit退出线程并传递一个整数作为退出状态
pthread_detach(pthread_self());
pthread_exit((void*)100); // 传递100作为退出状态
return NULL;
}
int main() {
pthread_t thread_id; // 线程ID
int* exit_status=NULL; // 用于存储线程退出状态的指针
// 创建线程
if(pthread_create(&thread_id, NULL, thread_function, NULL) != 0) {
perror("pthread_create");
exit(EXIT_FAILURE);
}
printf("Main thread continues doing other tasks...\n");
sleep(1); //一定要先分离,在等待
// 等待线程结束并获取退出状态
if ( pthread_join(thread_id, NULL ) == 0 ) {
printf("pthread wait success\n");
} else {
printf("pthread wait failed\n");
}
return 0;
}
测试的时候一定要注意先分离,再等待。
joinable和分离是冲突的,一个线程不能既是joinable又是分离的。
Joinable线程:默认情况下,新创建的线程是joinable的。这意味着它可以在完成执行后被其他线程通过
pthread_join
函数等待并获取其退出状态。如果一个joinable线程结束了但没有其他线程调用pthread_join
来等待它,那么它的资源(如栈空间)将不会被完全回收,直到某个线程成功调用了pthread_join
。分离(Detached)线程:分离线程在结束时会自动释放所有资源,不需要也不应该被其他线程调用
pthread_join
。线程可以通过调用pthread_detach(pthread_self())
函数自我分离,或者在创建时通过线程属性设置为分离状态,从而成为一个分离线程。
简而言之,一旦一个线程被标记为分离(通过调用pthread_detach
或创建时设置属性),它就不再是joinable的,其他线程就不能再通过pthread_join
来等待它。反之,如果一个线程保持joinable状态,那么它就应该在某个时刻被其他线程通过pthread_join
等待,否则可能会造成资源泄露。