目录
线程的基本特性
pthread库的主要函数
pthread_create
pthread_join
pthread_exit
pthread_mutex_init
pthread_mutex_lock 和 pthread_mutex_unlock
pthread_cond_init
pthread_cond_wait 和 pthread_cond_signal / pthread_cond_broadcast
pthread_cond_destroy
pthread_self
C++11提供的std::thread类
构造函数
成员函数
示例
c和c++ 线程创建差异 使用场景推荐
C 语言线程创建
C++ 语言线程创建
使用场景推荐
主线程退出,子线程会退出吗?
示例
示例
某个线程异常,会导致整个进程异常吗?
top -H和pstack
top -H
pstack
基本概念
线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix/Linux系统中,使用POSIX线程库(也称为pthread库)来支持多线程编程。
线程的基本特性
- 独立性:线程可以独立执行自己的线程体,它与其他线程共享进程的内存和其他资源。
- 并发性:线程是进程内的一条执行路径,因此多个线程可以在单核CPU上并发执行,通过CPU时间片轮转的方式实现。
- 共享性:线程共享进程的资源,包括内存空间、打开的文件描述符等。
pthread库的主要函数
- pthread_create:创建新线程。
- pthread_exit:终止调用它的线程。
- pthread_join:等待指定的线程结束。
- pthread_mutex_init, pthread_mutex_lock, pthread_mutex_unlock, pthread_mutex_destroy:用于互斥锁的操作,实现线程同步。
- pthread_cond_init, pthread_cond_wait, pthread_cond_signal, pthread_cond_destroy:条件变量相关操作,用于线程间的协调。
- pthread_self:获取线程ID
pthread_create
功能:创建一个新线程。
原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
参数:
pthread_t *thread
:指向线程标识符的指针,用来获取新创建线程的ID。const pthread_attr_t *attr
:线程属性,通常为NULL,表示使用默认属性。void *(*start_routine) (void *)
:线程函数指针,线程启动后执行的函数。void *arg
:传递给线程函数的参数。
示例:
#include <stdio.h>
#include <pthread.h>
void *print_hello(void *threadid) {
long tid;
tid = (long)threadid;
printf("Hello World! It's me, thread #%ld!\n", tid);
pthread_exit(NULL);
}
int main (int argc, char *argv[]) {
pthread_t threads[3];
int rc;
long t;
for(t = 0; t < 3; t++){
printf("In main: creating thread %ld\n", t);
rc = pthread_create(&threads[t], NULL, print_hello, (void *)t);
if (rc){
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(-1);
}
}
// 等待所有线程完成
for(t = 0; t < 3; t++) {
pthread_join(threads[t], NULL);
}
pthread_exit(NULL);
}
pthread_join
功能:等待一个特定的线程终止。
原型:
int pthread_join(pthread_t thread, void **retval);
参数:
pthread_t thread
:要等待的线程的ID。void **retval
:指向一个指针的指针,用来获取线程的返回值,如果不需要可以设为NULL。
示例:在上面的main
函数中已经展示了如何使用pthread_join
来等待线程完成。
pthread_exit
功能:终止调用它的线程。
原型:
void pthread_exit(void *retval);
参数:
void *retval
:线程的返回值,可以被其他线程通过pthread_join
获取。
示例:在print_hello
函数中使用了pthread_exit(NULL);
来终止线程。
pthread_mutex_init
功能:初始化互斥锁。
原型:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
参数:
pthread_mutex_t *restrict mutex
:指向互斥锁的指针。const pthread_mutexattr_t *restrict attr
:互斥锁属性,通常为NULL。
pthread_mutex_lock
和 pthread_mutex_unlock
功能:分别用于锁定和解锁互斥锁。
原型:
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数:
pthread_mutex_t *mutex
:指向互斥锁的指针。- 示例:
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
// 锁定互斥锁
pthread_mutex_lock(&mutex);
// 临界区代码...
// 解锁互斥锁
pthread_mutex_unlock(&mutex);
pthread_mutex_destroy(&mutex);
pthread_cond_init
功能:初始化条件变量。
原型:
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
参数:
pthread_cond_t *restrict cond
:指向条件变量的指针。const pthread_condattr_t *restrict attr
:条件变量属性,通常为NULL。
pthread_cond_wait
和 pthread_cond_signal
/ pthread_cond_broadcast
功能:pthread_cond_wait
用于等待条件变量被信号触发,pthread_cond_signal
用于唤醒等待该条件变量的一个线程,pthread_cond_broadcast
用于唤醒等待该条件变量的所有线程。
原型:
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
参数:
pthread_cond_t *cond
:指向条件变量的指针。pthread_mutex_t *mutex
:在pthread_cond_wait
中,指向互斥锁的指针,该互斥锁必须在调用pthread_cond_wait
之前被锁定,并且在等待期间会被自动解锁,在条件变量被信号触发并返回时会被重新锁定。
pthread_cond_destroy
功能:销毁条件变量。
原型:
int pthread_cond_destroy(pthread_cond_t *cond);
参数:
pthread_cond_t *cond
:指向要销毁的条件变量的指针。
示例(条件变量和互斥锁结合使用):
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex;
pthread_cond_t cond;
int ready = 0;
void *thread_function(void *arg) {
pthread_mutex_lock(&mutex);
while (!ready) {
pthread_cond_wait(&cond, &mutex);
}
printf("Thread is ready to work!\n");
pthread_mutex_unlock(&mutex);
pthread_exit(NULL);
}
int main() {
pthread_t thread;
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
pthread_create(&thread, NULL, thread_function, NULL);
// 模拟一些准备工作
sleep(1);
pthread_mutex_lock(&mutex);
ready = 1;
pthread_cond_signal(&cond); // 唤醒等待的线程
pthread_mutex_unlock(&mutex);
pthread_join(thread, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
pthread_exit(NULL);
}
在这个示例中,我们创建了一个线程thread_function
,它会在条件变量cond
上等待,直到主线程通过pthread_cond_signal
发送信号。主线程在发送信号前会先锁定互斥锁mutex
,更改ready
变量的值,然后发送信号并解锁互斥锁。这样,等待的线程在收到信号后会继续执行。
pthread_self
功能:pthread_self
是一个 POSIX 线程(pthreads)库中的函数,用于获取当前线程的线程标识符(thread identifier)。线程标识符是一个非零的无符号整数,它在其进程中唯一地标识一个线程。
原型:
pthread_t pthread_self(void);
参数
该函数没有参数。
返回值
pthread_self
返回一个 pthread_t
类型的值,表示调用线程的线程标识符。这个值在调用线程的生命周期内是唯一的,并且在进程中的所有线程中都是不同的。
使用场景
pthread_self
在多线程编程中非常有用,尤其是在需要区分不同线程或跟踪线程状态的场景中。例如,你可能在一个线程中存储了一些与线程相关的数据,并希望用线程标识符作为键来访问这些数据。
示例
下面是一个简单的示例,展示了如何使用 pthread_self
:
#include <stdio.h>
#include <pthread.h>
void *thread_function(void *arg) {
pthread_t thread_id = pthread_self();
printf("Thread ID: %lu\n", (unsigned long)thread_id);
return NULL;
}
int main() {
pthread_t thread1, thread2;
int rc1, rc2;
// 创建两个线程
rc1 = pthread_create(&thread1, NULL, thread_function, NULL);
rc2 = pthread_create(&thread2, NULL, thread_function, NULL);
if (rc1 || rc2) {
printf("Error: return code from pthread_create() is %d\n", rc1);
return 1;
}
// 等待线程结束
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
return 0;
}
在这个示例中,我们创建了两个线程,并在每个线程中调用 pthread_self
来获取并打印其线程标识符。这样,我们就可以看到每个线程都有一个唯一的标识符。
Linux下的线程操作函数提供了创建、等待、退出线程以及互斥锁和条件变量的基本功能。正确理解和使用这些函数是编写高效且安全的多线程程序的关键。每个函数都有其特定的用途和参数要求,程序员应该根据具体需求来选择合适的函数,并确保正确使用。同时,还需要注意线程同步和互斥的问题,以避免出现竞态条件和其他线程相关的问题。
C++11提供的std::thread类
std::thread
是 C++11 标准库中提供的一个类,用于表示和管理线程。它简化了线程创建和管理的复杂性,使得开发者可以更加容易地编写多线程程序。
构造函数
std::thread
类有多个构造函数,用于创建线程。
-
默认构造函数:
std::thread() noexcept;
创建一个空的线程对象,表示没有关联的线程。
-
初始化构造函数:
std::thread( thread_function, Args&&... args );
创建一个新线程,该线程将执行 thread_function
函数,并传递 args
作为参数。
成员函数
-
join:
void join();
阻塞当前线程,直到调用 join
的线程完成执行。
-
detach:
void detach();
将线程分离,允许它独立运行。一旦线程被分离,就不能再调用 join
。
-
swap:
void swap( thread& other ) noexcept;
交换两个线程对象。
-
get_id:
thread::id get_id() const noexcept;
返回线程的 ID。
-
hardware_concurrency(静态成员函数):
static unsigned int hardware_concurrency() noexcept;
返回可用的硬件并发线程数。
-
native_handle:
native_handle_type native_handle();
返回与 std::thread
对象关联的底层实现的句柄。
-
is_joinable:
bool is_joinable() const noexcept;
如果线程可以调用 join
,则返回 true
。
示例
下面是一个简单的 std::thread
使用示例:
#include <iostream>
#include <thread>
#include <chrono>
void thread_function(int n) {
for (int i = 0; i < n; ++i) {
std::cout << "Thread function is running..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
int main() {
std::thread t(thread_function, 5); // 创建新线程,执行 thread_function 函数,参数为 5
std::cout << "Main thread is running..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(3));
t.join(); // 等待新线程完成
return 0;
}
在这个例子中,我们创建了一个新线程 t
,它执行 thread_function
函数,并传递参数 5
。主线程和新线程会同时运行,但主线程会在 3 秒后通过调用 t.join()
等待新线程完成。这样,我们确保了主线程不会在新线程完成之前退出。
c和c++ 线程创建差异 使用场景推荐
C 和 C++ 在线程创建方面存在一些差异,这主要源于它们各自的标准库和编程模型的不同。以下是对这两个语言在线程创建方面的比较以及使用场景推荐:
C 语言线程创建
在 C 语言中,创建线程通常依赖于操作系统提供的线程 API 或第三方库,如 POSIX 线程(pthreads)。pthreads 是一套在 Unix-like 系统(包括 Linux 和 macOS)上广泛使用的线程库。
C++ 语言线程创建
C++11 标准引入了 <thread>
头文件,它提供了一个高级别的、跨平台的线程库。这使得在 C++ 中创建和管理线程变得相对简单和直接。
使用场景推荐
C 语言:
- 当你需要编写跨平台代码,并且目标平台可能不支持 C++11 或更高版本的特性时,C 语言和 pthreads 是一个不错的选择。
- 如果你正在维护一个现有的 C 语言项目,并且需要添加多线程功能,使用 pthreads 或其他类似的库可以保持代码的一致性。
- 对于性能敏感的应用,C 语言和底层的线程 API 可能会提供更细粒度的控制和更高的性能。
C++ 语言:
- 当你可以使用 C++11 或更高版本时,使用
std::thread
通常更为方便和直观。它提供了更高级别的抽象,减少了与操作系统底层 API 的直接交互。 std::thread
与 C++ 的其他特性(如 RAII、异常处理等)结合得更好,使得编写健壮的多线程代码更为容易。- 在编写新的 C++ 项目或重构旧项目时,如果需要使用多线程,优先考虑使用
std::thread
。 - 使用这个类可以将任意签名形式的函数作为线程函数
示例
#include<thread>
#include<iostream>
void fun1()
{
while(1)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout<<__func__<<" running"<<std::endl;
}
}
void fun2(int a)
{
while(1)
{
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout<<__func__<<" running param = "<<a<<std::endl;
}
}
int main()
{
std::thread t1(fun1);
std::thread t2(fun2,100);
t1.join();
t2.join();
return 0;
}
编译运行
- std::thread 对象在线程函数运行期间必须是有效的
示例
#include<thread>
#include<iostream>
void fun1()
{
while(1)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout<<__func__<<" running"<<std::endl;
}
}
void fun2()
{
std::thread t1(fun1);
}
int main()
{
fun2();
while(1);
return 0;
}
编译运行
崩溃的原因是,在fun2函数调用结束后,fun2中的局部变量 t1(线程对象)被销毁,而此时线程函数仍在运行。所以在使用 std::thread类时,必须保证在线程函数运行期间其线程对象有效。std::thread对象提供了一个 detach方法,通过这个方法可以让线程对象与线程函数脱离关系,这样即使线程对象被销毁,也不影响线程函数的运行。我们只需在func函数中调用detach方法即可
修改如下
#include<thread>
#include<iostream>
void fun1()
{
while(1)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout<<__func__<<" running"<<std::endl;
}
}
void fun2()
{
std::thread t1(fun1);
t1.detach();
}
int main()
{
fun2();
while(1);
return 0;
}
编译运行
总的来说,选择使用 C 还是 C++ 来创建线程取决于你的具体需求、项目约束以及你对这两个语言的熟悉程度。在可能的情况下,使用 C++ 的 std::thread
通常是一个更现代、更便捷的选择。
主线程退出,子线程会退出吗?
在Linux系统中,主线程的退出方式决定了子线程是否也会随之退出。如果主线程以return
的方式退出,实际上会调用exit()
函数,这个函数会执行关闭IO操作以及关闭其他子线程的操作,因此子线程会随之退出。
示例
#include<pthread.h>
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
void* test1(void *)
{
while(1)
{
usleep(300000);
printf("test1 \n");
}
}
void* test2(void *)
{
while(1)
{
usleep(300000);
printf("test2 \n");
}
}
int main()
{
pthread_t pth1;
pthread_t pth2;
int iRet1 = pthread_create(&pth1,nullptr,test1,nullptr);
int iRet2 = pthread_create(&pth2,nullptr,test2,nullptr);
// pthread_join(pth1,nullptr);
// pthread_join(pth2,nullptr);
printf("Main: program completed. Exiting.\n");
return 0;
// pthread_exit(NULL);
}
编译执行
然而,如果主线程以pthread_exit()
函数的方式退出,实际上是提前结束了主线程,此时并不会执行后续的exit()
函数。因此,这种方式可以达到主线程退出而子线程继续运行的目的。
示例
#include<pthread.h>
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
void* test1(void *)
{
while(1)
{
usleep(300000);
printf("test1 \n");
}
}
void* test2(void *)
{
while(1)
{
usleep(300000);
printf("test2 \n");
}
}
int main()
{
pthread_t pth1;
pthread_t pth2;
int iRet1 = pthread_create(&pth1,nullptr,test1,nullptr);
int iRet2 = pthread_create(&pth2,nullptr,test2,nullptr);
// pthread_join(pth1,nullptr);
// pthread_join(pth2,nullptr);
printf("Main: program completed. Exiting.\n");
pthread_exit(NULL);
return 0;
// pthread_exit(NULL);
}
这种情况也会导致进程变成僵尸进程。这是一种不好的做法,在实际开发中应该避免产生僵尸进程
总的来说,在Linux系统中,主线程和子线程之间没有必然的退出次序关系。主线程退出,子线程可以继续执行;子线程退出,主线程也可以继续执行。但值得注意的是,如果进程终止,那么进程下所有的线程都会终止。
此外,需要注意的是,如果主线程退出而没有等待其他子线程执行完毕,可能会导致进程变成僵尸进程。僵尸进程是应该被避免的,因为它们会占用系统资源。因此,在实际编程中,应该确保主线程在退出之前等待其他子线程执行完毕,可以通过调用pthread_exit()
等方法来实现。
综上所述,主线程退出时子线程是否会退出,取决于主线程的退出方式以及是否采取了适当的措施来避免产生僵尸进程。
某个线程异常,会导致整个进程异常吗?
在Linux中,一个线程的异常通常不会直接导致整个进程异常。每个线程都有自己的执行上下文和堆栈,因此一个线程的异常通常只会影响到该线程本身,而不会影响到其他线程或整个进程的稳定性。
然而,存在一些情况下,一个线程的异常可能会影响到整个进程的正常运行,例如:
-
未捕获的异常:如果一个线程中的异常没有被正确捕获处理,那么这个异常可能会导致整个进程异常终止。这通常发生在没有适当的异常处理机制的情况下,例如没有使用 try-catch 块捕获异常。
-
资源泄漏:线程异常可能会导致资源泄漏,如果这些资源没有被正确释放,可能会影响到整个进程的性能或稳定性。
-
竞态条件:线程异常可能会导致竞态条件或其他线程安全问题,这可能会导致进程的不确定行为或崩溃。
虽然一个线程的异常通常不会直接导致整个进程的异常,但是它可能会间接地影响到进程的稳定性。因此,确保线程中的异常能够被正确地捕获和处理是保障整个进程稳定性的重要一环。
top -H和pstack
top -H
和 pstack
是两个在 Linux 系统中用于诊断和分析多线程进程的工具。下面我会为你详细解释这两个命令的用途和工作原理。
top -H
top
命令是一个常用的性能分析工具,它可以实时显示系统中各个进程的资源占用状况,类似于 Windows 的任务管理器。默认情况下,top
显示的是各个进程的总体信息,而不会区分进程中的各个线程。
-H
选项是 top
的一个参数,用于使 top
以线程模式显示信息。当使用这个选项时,top
会列出每个进程的所有线程,并显示每个线程的 CPU 使用率、内存使用情况等信息。这对于分析多线程应用程序的性能问题非常有用。
使用 top -H
,你可以观察各个线程的资源占用情况,从而找出可能的性能瓶颈或问题所在。
pstack
pstack
是一个用于显示进程栈跟踪信息的工具。它可以打印出指定进程的调用栈信息,从而帮助你分析进程在某一时刻的执行状态。
当你怀疑一个进程可能陷入了死循环或者出现了其他异常行为时,pstack
可以帮助你查看该进程当前正在执行的代码路径。通过比较不同时间点的栈跟踪信息,你可以了解进程的执行流程,并找出可能的问题所在。
使用 pstack
时,你需要提供进程的 PID(进程 ID)作为参数。例如,pstack 12345
将显示 PID 为 12345 的进程的栈跟踪信息。
这两个工具在 Linux 系统的性能分析和调试中非常有用,特别是在处理多线程应用程序时。它们可以帮助你深入了解进程和线程的执行状态,从而找出并解决潜在的问题。