在Linux操作系统内部,不管是进程还是线程。它以PCB为准,只有在用户态里才有线程的概念。一般实现线程会用到一个POSIX线程库,在这里可以通过调用POSIX库里的函数来实现有关线程的各种操作。不过内核中也有一种内核级线程。
用户级线程:管理过程全部由用户程序完成,操作系统内核心只对进程进行管理。如POSIX线程库。
系统级线程(核心级线程):由操作系统内核进行管理。操作系统内核给应用程序提供相应的系统调用和应用程序接口API,以使用户程序可以创建、执行、撤消线程。
POSIX线程库:
与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以 “pthread_” 打头的要使用这些函数库,要通过引入头文 <pthread.h>链接这些线程函数库时要使用编译器命令的 “-lpthread” 选项
一、创建线程
1 .线程创建函数
与fork父子进程类似,创建出新进程后,新线程去执行划分给他的函数。主线程继续往下执行自己的代码。这里不分先后的执行。
#include <pthread.h>int pthread_create(pthread_t *thread, /* 线程id,将线程id存入,线程标识符,线程栈的起始地址,输出型参数,用以获取创建成功的线程 ID */const pthread_attr_t *attr, /* 设置创建线程的属性,使用 nullptr 表示使用默认属性 */void *(*start_routine) (void *), /* 函数地址,该线程启动后需要去执行的函数 */void *arg); /* 线程要去执行的函数的形参,没参数时可填写 nullptr *///返回值:成功返回0,失败返回错误码,如://EAGAIN 描述: 超出了系统限制,如创建的线程太多。 //EINVAL 描述: tattr 的值无效。
错误检查:
传统的一些函数是,成功返回 0 ,失败返回 -1 ,并且对全局变量 errno 赋值以指示错误。pthreads 函数出错时不会设置全局变量 errno (而大部分其他 POSIX 函数会这样做)。而是将错误代码通过返回值返回pthreads 同样也提供了线程内的 errno 变量,以支持其它使用 errno 的代码。对于 pthreads 函数的错误,建议通过返回值业判定,因为读取返回值要比读取线程内的errno 变量的开销更小
例:
// 新线程
void* fun(void* args)
{while (true){std::cout << "新线程正在运行" << std::endl;sleep(1);}
}// 主线程
int main()
{// 记录线程 idpthread_t tid;// 创建新线程,并让新线程去执行 fun 函数if(pthread_create(&tid, nullptr, fun, nullptr)!=0){perror("pthread_create:");exit(-1);}// 主线程继续执行自己后续的代码while (true){std::cout << "主线程正在运行" << std::endl;sleep(1);}return 0;
}
执行程序:主新进程各自自行,互不干涉
我们可以打开查看进程的监控和查看轻量级进程的监控:
可以发现,进程只有一个。线程有两个,其中一个线程的LWP与PID一样,这是主进程。比主进程LWP大1的是新进程。
解释:
- LWP (Light Weight Process) 表示的就是线程 (轻量级进程) 的 ID,其中主线程的 LWP 值等同于进程的 PID 值。
2 .线程传参
pthread_create函数创建出来的线程所执行的函数的形参类型是 void* ,可以传递任何类型的参数,当然也能传对象给线程使用。
例:这里我将新线程命名传给新进程,新进程执行打印自己名字的函数
struct Data
{Data(const std::string &name): _name(name){}std::string GetName(){return _name;}std::string _name;
};void *fun(void *args)
{Data *td = static_cast<Data *>(args);while (true){std::cout << "新线程 " << td->GetName() << " 正在运行" << std::endl;sleep(1);}
}int main()
{Data *d = new Data("thread 1");// 创建新线程,将d对象作为fun函数的参数pthread_t tid;pthread_create(&tid, nullptr, fun, d);while (true){std::cout << "主线程正在运行" << std::endl;sleep(1);}return 0;
}
执行结果:
3 . 线程ID及进程地址空间布局
问题:tid 是什么样子的——虚拟地址
pthread_t pthread_self ( void );
可以看见,每个线程有自己独立的栈和空间和数据。
pthread_create创建进程函数会将 tid 修改为现场控制块的地址。 未来我们只需要找到线程控制块的地址即可;
4. 创建多线程
- 每个线程都由各自的线程 ID 所管理着,创建多线程时可以使用一个数据结构将每个创建出来的线程的 ID 管理起来。
例:
struct Data
{Data(const std::string &name): _name(name){}std::string thread_name(){return _name;}std::string _name;
};void *thread_run(void *args)
{Data *td = static_cast<Data *>(args);while (true){std::cout << "新线程 " << td->thread_name() << " 正在运行" << std::endl;sleep(1);}return nullptr;
}const int gnum = 5; // 5 个线程int main()
{// 存储创建的所有线程的线程 IDstd::vector<pthread_t> tids(gnum);// 创建 5 个线程并给每个线程传递一个 Data 对象,都执行打印自己名字的函数for (size_t i = 0; i < gnum; i++){std::string name = "thread " + std::to_string(i + 1);Data *td = new Data(name);pthread_create(&tids[i], nullptr, thread_run, td);}while (true){std::cout << "主线程正在运行" << std::endl;sleep(1);}return 0;
}
执行结果:
使用
ps -aL
指令打开监控可以查到主和新线程这 6 个线程的情况。
二、线程终止
如果只是想终止某个线程而不是整个进程,可以有如下 3 种方法
1. 从线程函数 return 。这种方法对主线程不适用 , 从 main 函数 return 相当于调用 exit 。2. 线程可以调用 pthread_ exit 终止自己。3. 一个线程可以调用 pthread_ cancel 终止同一进程中的另一个线程。
1 .使用 return 终止线程
- return的作用是退出函数。在线程调用的函数中使用 return 退出当前线程(执行完毕,没有代码执行了,就退出了),但是在main函数中使用return代表整个进程退出(main函数执行完毕),也就是说只要主线程退出了那么整个进程就退出了,此时该进程曾经申请的资源就会被释放,而其他线程会因为没有了资源,自然而然的也退出了。
2 .使用 pthread_exit 函数终止线程
pthread_exit 是 POSIX 线程(也称为 pthreads)库中的一个函数,用于显式地终止调用它的线程。当线程通过 pthread_exit
函数退出时,它可以返回一个指向某个对象的指针,这个对象可以是任何类型的指针。需要注意的是,返回的指针不会被自动释放,因此接收者(如通过 pthread_join
获取退出状态的线程)需要负责适当地处理这个指针(例如,释放分配的内存)。
因此:函数返回的指针所指向的内存单元必须是全局的或者是用malloc (new)分配的,不能在线程函数的栈上分配,因为当其他线程得到这个返回指针时,线程函数已经退出了。
#include <pthread.h>
void pthread_exit(void *retval);//retval:这是一个指向任意类型的指针,通常用于表示线程的退出状态或返回值。如果不需要返回值,可以传递 NULL。
注意:
- 线程一旦调用
pthread_exit
,就会立即终止执行,其调用之后的任何代码都不会被执行。- 如果线程通过
pthread_exit
退出,那么该线程应占用的资源(如栈空间)将由 pthread 库自动回收。- 使用
pthread_exit
退出的线程不会释放它通过malloc
、new
等方式分配的内存。这些内存必须在线程退出前由程序员显式释放。- 线程可以通过
pthread_join
函数等待另一个线程的终止,并获取该线程的返回值(通过pthread_exit
传递的retval
参数)。
示例:
void* thread_function(void* arg) { // 线程的工作内容 printf("Thread is running\n"); // 假设我们在这里完成了工作,现在准备退出 // 返回一个指向整数的指针,表示退出状态 int* status = (int*)malloc(sizeof(int)); *status = 0; // 假设 0 表示成功 当然也可以传入其他类型指针,强转就行了pthread_exit((void*)status); // 以下代码不会被执行,因为线程已经通过 pthread_exit 退出了 printf("This line will not be executed\n"); // free(status);
}
int main() { pthread_t thread; // 创建线程 pthread_create(&thread, NULL, thread_function, NULL); int* retval=nullptr;// 等待线程结束,并获取其返回值 pthread_join(thread, (void**)&retval); // 处理线程的返回值 if (thread_return != NULL) { printf("Thread returned: %d\n", *thread_return); free(thread_return); // 释放之前线程函数分配的内存 } return 0;
}
执行结果:
3 . 使用 pthread_cancel 函数终止线程
pthread_cancel 是 POSIX 线程(pthread)库中的一个函数,用于向指定的线程发送取消请求,请求该线程终止执行。这个函数的行为和效果受到多个因素的影响,包括线程对取消请求的反应状态、取消类型的设置以及线程中取消点的存在与否。
#include <pthread.h>
int pthread_cancel(pthread_t thread);//thread:指定要取消的线程的标识符(ID)。
返回值:
- 成功时返回
0
。- 失败时返回错误码。
取消请求的处理
取消点:线程在接收到取消请求后,并不会立即终止。它将继续执行,直到到达一个取消点(Cancellation Point)。取消点是线程检查是否被取消并按照请求进行动作的一个位置。这些取消点通常是特定的系统调用或库函数。与信号有一点类似,在特定的点进行检测执行。
注意:
pthread_cancel
调用本身并不等待线程终止,它只是发送了一个取消请求。如果需要确保线程已经终止,可以使用pthread_join
函数。- 线程在无限循环或没有自然取消点的情况下,可能无法响应取消请求。在这种情况下,可以通过在循环体中添加
pthread_testcancel
调用来手动创建取消点。- 取消一个线程可能会导致资源释放的问题,特别是如果线程在持有锁或其他资源时被取消。因此,在编写多线程程序时,需要仔细考虑如何安全地释放这些资源。
例如:新线程执行5秒后被主线程取消
void* thread_run(void* args)
{std::string thread_name = static_cast<const char*>(args);while (true){std::cout << thread_name << " 正在运行" << std::endl;sleep(1);}
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, thread_run, (void*)"thread -1");std::cout << "主线程正在运行" << std::endl;sleep(5);pthread_cancel(tid); // 主线程取消新线程std::cout << "新线程已被终止" << std::endl;return 0;
}
执行结果:
三、等待线程
问题:为什么需要线程等待?
已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。创建新的线程不会复用刚才退出线程的地址空间。
1 .线程等待函数
- 该函数在等待成功时会返回 0,失败时返回对应的错误码。
#include <pthread.h>int pthread_join(pthread_t thread, /* 被等待的线程的线程 ID */ void **retval); /* 获取被等待的线程在退出时的返回值 */
- 调用该函数的线程将阻塞等待新线程,直到所等待的的新线程终止为止,被等待的线程以不同的方式终止时,通过 pthread_join 得到的终止状态也是不同的。
2 .获取返回值
- pthread_create 创建出的线程所调用函数的返回值是一个 void* 类型的值,而 pthread_join 函数的第二个输出型参数是 void** 类型,就是为了获取新线程所调用函数的返回值。
例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h> // 线程运行的函数
void* threadFunction(void* arg) { // 假设我们不需要传递任何参数给线程函数 // 这里的arg参数只是为了满足pthread_create的签名 (void)arg; // 动态分配一个字符串 char* message = strdup("Hello from thread!"); if (message == NULL) { // 如果内存分配失败,则线程返回NULL return NULL; } // 线程返回字符串的地址 return (void*)message;
} int main() { pthread_t threadId; void* threadResult; char* message; // 创建一个新线程 if (pthread_create(&threadId, NULL, threadFunction, NULL) != 0) { printf("Error: unable to create thread\n"); return 1; } // 等待线程结束并获取其返回值 if (pthread_join(threadId, &threadResult) != 0) { printf("Error: unable to join thread\n"); return 1; } // 将void*类型的返回值转换为char*类型 message = (char*)threadResult; // 打印字符串 printf("%s\n", message); // 释放字符串以避免内存泄漏 free(message); return 0;
}
执行结果:
我们也可以返回自定义类型(对象)。这可以让我们得到线程执行任务结束后给我们返回执行的结果。但是要记住释放返回的内存。
1. 如果 thread 线程通过 return 返回 ,value_ ptr 所指向的单元里存放的是 thread 线程函数的返回值。2. 如果 thread 线程被别的线程调用 pthread_ cancel 异常终掉 ,value_ ptr 所指向的单元里存放的是常数PTHREAD_ CANCELED。3. 如果 thread 线程是自己调用 pthread_exit 终止的 ,value_ptr 所指向的单元存放的是传给 pthread_exit 的参数。4. 如果对 thread 线程的终止状态不感兴趣 , 可以传 NULL 给 value_ ptr 参数。
四、分离线程
1 .线程分离概念
一般情况下,主线程是需要阻塞等待新线程的,如果不等待线程退出,就有可能造成类似 “僵尸” 问题。
但如果主线程就是不想等待新线程退出,也不关心新线程的返回值,此时就可以将新线程分离,被分离的线程在退出时会自动释放资源。类似信号设置为忽略一样。
分离出的新线程依旧要使用进程的资源,且依旧在该进程内运行,甚至这个新线程崩溃了也会影响整个进程。线程分离只是让主线程不需要再阻塞等待整个新线程罢了,在新线程退出时系统会自动回收该线程所持有的资源。
一个线程可以将其他线程分离出去,也可以将自己分离出去。
pthread_detach(pthread_self());
等待和分离会发生冲突,一个线程不能既是可被等待的又是分离出去的。
虽然分离出去的线程已经不归主线程管了,但一般还是建议让主线程最后再退出。
分离出去的线程可以被 pthread_cancel 函数终止,但不能被 pthread_join 函数等待。
2 .线程分离函数
#include <pthread.h>int pthread_detach(pthread_t thread); // thread 是要分离出去的线程的 ID//线程分离成功时返回 0,失败时则返回对应错误码。
例:
void* thread_function(void* arg) { // 线程执行的代码 printf("Thread is running\n"); return nullptr;
} int main() { pthread_t thread_id; int result; // 创建线程 result = pthread_create(&thread_id, NULL, thread_function, NULL); if (result != 0) { perror("Failed to create thread"); exit(EXIT_FAILURE); } // 将线程设置为分离状态 result = pthread_detach(thread_id); if (result != 0) { perror("Failed to detach thread"); exit(EXIT_FAILURE); } sleep(3);int n=pthread_join(thread_id,nullptr);std::cout<<"pthread_join return val : "<<n<<std::endl; return 0;
}
执行结果:
可以看到,线程分离后pthread_join的返回值是22,说明等待出错。