线程概念
什么是线程:
线程(Thread)是操作系统能够进行运算调度的最小单位. 它被包含在进程之中, 是进程中的实际运作单位. 一个进程可以包含多个线程.
进程 : 线程 == 1 : n (n >= 1).
进程是系统分配资源的基本单位.
线程则是系统调度的基本单位.
在 Linux 中, 线程又被称为轻量级进程. 因为线程不必再进行空间的分配. 只需要对新创建的线程进行初始化 (创建线程的PCB即可).
可以看到, 如果要新建线程, 只需要创建相应的 线程控制块(TCB) 即可, TCB 和 PCB中的内容非常相近.
在 Linux 中是没有单独实现线程的. Linux 中的线程都是通过进程来实现的.
所以在 Linux 实际上是没有线程的, 站在调度的视角来看, 调度的都是进程不存在线程.
线程与进程区别
1. 进程是系统资源分配的基本单位
2. 线程是系统调度的基本单位
虽然线程共享进程的资源, 但是每个进程也都会有只属于自己的数据和资源.
- 线程ID
- 每个线程都有独属于自己的栈空间
- 既然线程是调度的基本单位,那每个线程的调度优先级也有可能是不同的
线程共享的如: 代码段, 全局变量, 信号的处理 ....
可以使用指令: ps -L. 来查看所有的进程和线程
LWP 就是线程ID. PID 和 LWP 相同的线程就是这个进程的主线程
PID 相同的线程则属于同一个进程.
如何操作线程
上面提到了, Linux 系统本身没有为线程单独实现, 但是提供了一个第三方库: <pthread>.
使用 <pthread> 库就能在 Linux 下完成线程的操作.
因为这个库是第三方库, 所以在使用时, 使用G++编译时需要加入选项 "-lpthread"
g++ -o text.exe test.cpp -lpthread
创建线程
了解 pthread_create 函数:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
// thread:指向 pthread_t 类型的指针, 用于存储新创建的线程ID.
// attr:指向 pthread_attr_t 类型的指针, 用于设置线程属性. 通常传 NULL 表示使用默认属性.
// start_routine:线程函数的指针.
// arg:传递给线程函数的参数.
创建线程的例子:
#include<pthread.h>
void* func(void* args)
{
printf("thread func running\n");
return NULL;
}
int main()
{
pthread_t thread; // 用来记录创建出来的线程的线程ID
ret = pthread_create(&thread, NULL, func, NULL); // 创建线程
if(ret != 0)
{
perror("创建线程失败");
return 1;
}
pthread_join(thread, NULL); // 和进程一样, 创建了子进程后, 需要等待子进程结束
// 这里同样需要等待线程结束
return 0;
}
在上面的代码中, 将线程创建出来后, 线程就会独立的去运行 func 函数, 当 func 函数结束时, 线程也同样结束了. 线程只会运行传给他的函数.
线程终止
1. return 返回
在上面的例子中, 我们等待创建出来的线程运行到末尾, 执行 return 语句.
执行完 return 语句后, 线程就结束了.
2. 使用函数 pthread_exit()
pthread_exit
函数可以立即终止当前线程, 并返回指定的值.
#include<pthread.h>
void* func(void* arg)
{
printf("thread is running\n");
pthread_exit(); // 线程执行这一句后, 本线程就会被终止
printf("no running\n");
}
3. 使用 pthread_cancel() 函数
void* func(void* arg)
{
}
int main()
{
pthread_t thread;
if (pthread_create(&thread, NULL, func, NULL) != 0)
{
perror("pthread_create");
return 1;
}
// 请求终止线程
if (pthread_cancel(thread) != 0) // 将线程的 ID 传给函数
{
perror("pthread_cancel");
return 1;
}
return 0;
}
以上三种方法在线程终止后, 都可以通过 pthread_join() 函数进行线程等待.
int pthread_join(pthread_t thread, void **retval);
thread
:要等待的线程的 ID. 这个 ID 是通过pthread_create
函数创建线程时返回的.
retval
:指向void *
类型的指针, 用于存储线程函数的返回值. 如果不需要获取线程的返回值, 可以传NULL.
pthread_join()
等待线程终止后, 会自动释放线程的资源. 如果线程没有被等待, 其资源会在线程终止时自动释放, 但可能会导致资源泄漏.
线程分离
如果我们并不关心线程的运行情况, 我们也可以选择不进行线程等待, 这个线程在运行结束后, 就会自动释放资源, 不需要我们调用 pthread_join() 函数.
我们需要将线程设置为分离状态, 这样主线程就不用调用 pthread_join().
int pthread_detach(pthread_t thread);
#include <pthread.h>
void* thread_function(void* arg)
{
// 线程要执行的代码
printf("Thread function is running.\n");
return NULL;
}
int main()
{
pthread_t thread;
// 创建线程
if (pthread_create(&thread, NULL, thread_function, NULL) != 0)
{
perror("pthread_create");
return 1;
}
// 将线程设置为分离状态
if (pthread_detach(thread) != 0)
{
perror("pthread_detach");
return 1;
}
// 主线程继续执行
printf("Main thread is running.\n");
// 将上面创建的线程设置为分离状态后, 就不用调用 pthread_join 函数.
return 0;
}
线程优缺点
-
优点:
- 提高程序的响应性和并行处理能力
- 共享内存, 减少资源开销
- 轻量级, 调度开销小
- 适合细粒度的并发任务
-
缺点:
- 数据竞争和同步问题复杂
- 容易发生死锁, 难以检测和解决
- 线程管理开销, 创建和销毁线程消耗资源
- 优先级问题, 可能导致优先级倒置和调度不公平