Linux中的线程
Linux中线程的概念和控制
概念
线程是进程的内执行的,线程是在进程的地址空间内运行的。线程的执行粒度比进程更细。
任何执行流执行,都要有资源。线程就是利用了进程的进程地址空间资源。并且线程中的大部分资源都是共享的。
线程是操作系统调度的基本单位。而进程是承担操作系统分配资源的基本实体。
进程包含了线程,用来描述管理线程的结构是线程控制块(
TCB
,Thread Control Block)。在Linux中,线程和进程的管理都通过任务结构(task_struct)来实现,因此,Linux的线程和进程使用同样的机制进行管理。由于这种设计,Linux没有单独的TCB结构,而是复用了
PCB
结构来管理线程。因此,虽然Linux有线程的概念,但线程和进程在内核层面并没有本质区别。
线程的独立结构
栈空间:每个线程都有自己的栈,用于保存函数调用信息、本地变量等。栈是线程私有的。
线程局部存储(Thread Local Storage, TLS):每个线程可以有自己的线程局部存储区,用于存储线程特有的数据。
线程 ID(TID):每个线程有自己唯一的线程标识符。
线程的共享部分
进程的虚拟地址空间:
- 所有线程共享同一个进程的虚拟地址空间,因此它们可以访问相同的全局变量、堆内存和已映射的文件。线程之间可以通过共享内存区进行数据交换。
全局变量和静态变量:
- 线程可以共享进程中的全局变量和静态变量。这意味着一个线程修改了全局或静态变量,其他线程也会看到相同的变化,也因此可能要设置锁。
堆内存:
- 进程的所有线程都共享堆内存。这意味着一个线程分配的动态内存(通过
malloc
、new
等)可以被其他线程访问。
- 进程的所有线程都共享堆内存。这意味着一个线程分配的动态内存(通过
文件描述符(File Descriptors):
- 进程打开的文件描述符(包括套接字)在所有线程之间共享。一个线程可以读写另一个线程打开的文件或套接字。
当前目录(Current Working Directory):
- 所有线程共享进程的当前工作目录。这意味着如果一个线程更改了进程的当前目录(例如使用
chdir
函数),其他线程也会受到影响。
- 所有线程共享进程的当前工作目录。这意味着如果一个线程更改了进程的当前目录(例如使用
信号处理器:
- 线程共享同一个信号处理器设置。如果进程中的一个线程接收到信号,所有线程都可能被中断,或者根据信号的类型和设置,某个特定线程会处理该信号。
进程标识符(PID):
- 虽然每个线程都有一个唯一的线程标识符(TID),但它们共享同一个进程标识符(PID)。
用户 ID 和组 ID:
- 线程共享进程的用户 ID(UID)、有效用户 ID(EUID)和组 ID(GID)。这决定了线程对系统资源的访问权限。
进程间通信(IPC)机制:
- 线程可以共享进程的 IPC 机制,如管道、消息队列和共享内存段。
共享数据的不一致问题
如果有若干个执行流对一个共享数据的修改操作没有结束就被切走。那么可能导致共享数据的不一致。
因此在对共享数据进行访问的时候,我们需要保证只有一个执行流访问,即对进程进行加锁。
创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
线程的创建对应的函数是pthread_create(),线程不是一个完全由内核实现的机制,它是由内核态和用户态合作完成的。
- pthread_create()不是一个系统调用。
- thread:返回线程ID。
- attr:设置线程的属性,attr为NULL表示使用默认属性。
- start_routine:是个函数地址,线程启动后要执行的函数。
- arg:传给线程启动函数的参数,也可以传递类对象。
- 返回值:成功返回0;失败返回错误码。
如何获取线程的返回值?
int pthread_join(pthread_t thread, void **retval);
pthread_join
函数用于等待指定的线程终止,并获取其返回值。retval
是一个二级指针,其作用是存放线程返回值的指针。在线程函数中,可以通过
pthread_exit
或者直接返回一个指针类型的值来设置线程的返回值。在调用pthread_join
时,retval
指向的内存会被更新为线程的返回值。例如,在系统调用内,*retval = return_val
的方式,将线程的返回值拷贝给retval
指向的变量。
如何让线程执行不同的代码?
由于每个函数地址都不一样,那么只要让每个函数让不同的线程去跑,就已经实现了线程的分离。
线程的分离
线程分离是指在多线程编程中,主线程创建了一个子线程后,子线程与主线程脱离关系,成为独立的线程。
线程分离的作用:
避免资源泄露:通常,主线程在子线程完成时调用
pthread_join
来回收资源。如果不调用pthread_join
而子线程结束了,线程的资源将保持未释放的状态,导致资源泄露。通过线程分离,子线程完成后会自动释放资源。无需等待子线程完成:对于一些任务,主线程不关心子线程何时完成或完成状态,那么可以将子线程设置为分离状态,主线程继续执行其他工作,无需等待子线程完成。
1. 在创建线程时设置分离属性:
1 |
|
2. 在线程创建后,将其设置为分离状态:
1 |
|
注意事项:
- 一旦线程被分离,主线程不能再对该线程使用
pthread_join
,否则会导致错误。 - 分离线程主要适用于那些不需要主线程关注其执行结果的子线程。
线程分离对于资源管理非常有用,尤其是在需要创建大量线程的应用中,避免了主线程对每个子线程的 pthread_join
调用。
线程的切换
线程的切换比起进程切换更加轻量。
Cache
:CPU进行线程切换的时候,上下文会切换,但是Cache不会被重启缓存。只有在进程切换的时候,Cache
数据才会被重新缓存。其中Cache
中的数据叫做热数据
。
其中,刚启动的线程叫做主线程。
线程异常
单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃-。
线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出。
线程退出
- 线程执行完毕之后
return val
。 - 使用
pthread_exit()
。
线程用途
- 合理的使用多线程,能提高CPU密集型程序的执行效率。
- 合理的使用多线程,能提高IO密集型程序的用户体验。(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)
clone和pthread线程库
Linux中自带的用于创建“轻量级进程”的接口clone()
比较复杂且要求权限较高,于是我们使用被线程库pthread
(动态库)封装过的接口,使用起来更加方便安全。
线程库需要维护多个线程的属性集合,因此线程库中具有多个线程控制块(tcb)
其中,每一个线程都需要有自己的栈结构,用来保证每个线程的执行流独立。因此,除了主线程,其它线程的独立栈,都在共享区,已载入的pthread库中。
pthread_t
pthread_t
是 POSIX 线程库定义的线程标识符类型,其具体类型取决于实现。在 Linux 的 NPTL (Native POSIX Thread Library) 实现中,pthread_t
类型的线程 ID 通常为指针,对应于进程地址空间上的一个地址。这些实现细节对用户代码来说是透明的,用户只需要使用**pthread_t
类型的变量来表示和操作线程 ID**。
线程的局部存储
独立的栈结构,保证线程之间执行流独立。但实际上它们都还是处于同一个地址空间中的。
- 如果一个线程想要一个私有的全局变量。那么需要在声明全局变量的时候,需要使用__thread
(eg.__thread int num = 5)。这种技术即叫线程的局部存储,但是只能用来修饰内置类型,不能用来修饰自定义类型。
线程函数中的void *
线程中回调函数的返回值和参数类型为
void *
是为了提供最大的灵活性和通用性。参数类型为
void *
- 我们可以在创建线程时传递任何类型的数据指针。例如,可以传递一个指向整数、结构体或数组的指针。
- 类型转换(
static_cast
等方法):在线程函数内部,参数可以被转换回其原始类型并进行操作。这允许在线程函数中处理多种类型的数据。
返回值类型为
void *
void *
作为返回类型允许线程函数返回任意类型的结果。线程函数可以将结果存储在堆内存中,并返回一个指向该结果的指针。- 线程回收:当线程结束时,调用
pthread_join
函数可以接收线程函数的返回值。使用void **
类型的指针作为pthread_join
的第二个参数,可以接收并处理线程函数的返回值。
实验代码
1 |
|
线程相关的函数
函数 | 原型 | 描述 |
---|---|---|
pthread_create | int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg); |
创建一个新的线程。 |
pthread_exit | void pthread_exit(void *retval); |
终止调用线程,并返回一个值。 |
pthread_join | int pthread_join(pthread_t thread, void **retval); |
等待一个线程终止,获取返回值(默认是阻塞等待的)。 |
pthread_detach | int pthread_detach(pthread_t thread); |
分离线程,使其在终止时自动释放资源。 |
pthread_self | pthread_t pthread_self(void); |
获取调用线程的线程ID。 |
pthread_equal | int pthread_equal(pthread_t t1, pthread_t t2); |
比较两个线程ID是否相等。 |
pthread_cancel | int pthread_cancel(pthread_t thread); |
向线程发送取消请求(返回值会被设置为-1 )。 |
pthread_once | int pthread_once(pthread_once_t *once_control, void (*init_routine)(void)); |
确保某个初始化函数仅执行一次。 |
pthread_key_create | int pthread_key_create(pthread_key_t *key, void (*destructor)(void*)); |
创建线程特定数据的键。 |
pthread_key_delete | int pthread_key_delete(pthread_key_t key); |
删除线程特定数据的键。 |
pthread_setspecific | int pthread_setspecific(pthread_key_t key, const void *value); |
设置线程特定数据的值。 |
pthread_getspecific | void *pthread_getspecific(pthread_key_t key); |
获取线程特定数据的值。 |
pthread_mutex_init | int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); |
初始化互斥量。 |
pthread_mutex_destroy | int pthread_mutex_destroy(pthread_mutex_t *mutex); |
销毁互斥量。 |
pthread_mutex_lock | int pthread_mutex_lock(pthread_mutex_t *mutex); |
加锁互斥量。 |
pthread_mutex_trylock | int pthread_mutex_trylock(pthread_mutex_t *mutex); |
尝试加锁互斥量(非阻塞)。 |
pthread_mutex_unlock | int pthread_mutex_unlock(pthread_mutex_t *mutex); |
解锁互斥量。 |
pthread_cond_init | int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr); |
初始化条件变量。 |
pthread_cond_destroy | int pthread_cond_destroy(pthread_cond_t *cond); |
销毁条件变量。 |
pthread_cond_wait | int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); |
等待条件变量。 |
pthread_cond_signal | int pthread_cond_signal(pthread_cond_t *cond); |
发信号通知一个等待条件变量的线程。 |
pthread_cond_broadcast | int pthread_cond_broadcast(pthread_cond_t *cond); |
发信号通知所有等待条件变量的线程。 |
pthread_attr_init | int pthread_attr_init(pthread_attr_t *attr); |
初始化线程属性对象。 |
pthread_attr_destroy | int pthread_attr_destroy(pthread_attr_t *attr); |
销毁线程属性对象。 |
pthread_attr_setdetachstate | int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); |
设置线程分离状态属性。 |
pthread_attr_getdetachstate | int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate); |
获取线程分离状态属性。 |
pthread_attr_setschedpolicy | int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy); |
设置线程调度策略属性。 |
pthread_attr_getschedpolicy | int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy); |
获取线程调度策略属性。 |
pthread_attr_setschedparam | int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param); |
设置线程调度参数属性。 |
pthread_attr_getschedparam | int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param); |
获取线程调度参数属性。 |