一.多线程
1.什么是线程
要了解线程,首先需要知道进程。一个进程指的是一个正在执行的应用程序。线程对应的英文名称为“thread”,它的功能是执行应用程序中的某个具体任务,比如一段程序、一个函数等。
线程和进程之间的关系,类似于工厂和工人之间的关系,进程好比是工厂,线程就如同工厂中的工人。一个工厂可以容纳多个工人,工厂负责为所有工人提供必要的资源(电力、产品原料、食堂、厕所等),所有工人共享这些资源,每个工人负责完成一项具体的任务,他们相互配合,共同保证整个工厂的平稳运行。
每个进程执行前,操作系统都会为其分配所需的资源,包括要执行的程序代码、数据、内存空间、文件资源等。一个进程至少包含 1 个线程,可以包含多个线程,所有线程共享进程的资源,各个线程也可以拥有属于自己的私有资源。
进程仅负责为各个线程提供所需的资源,真正执行任务的是线程,而不是进程。
2.什么是多线程
所谓多线程,即一个进程中拥有多(≥2)个线程,线程之间相互协作、共同执行一个应用程序。
当进程中仅包含 1 个执行程序指令的线程时,该线程又称“主线程”,这样的进程称为“单线程进程”。
二.线程基础函数
1.pthread_self函数
作用:获取线程的ID,起作用对应于进程的getpid()函数
函数原型:
#include <pthread.h>
pthread_t pthread_self(void);
- 线程ID:
pthread_t类型,本质是Linux下的无符号整数 - 线程ID是进程内部识别的标志(两个进程间,线程ID允许相同)
2.pthread_create函数
作用:用于创建新线程
函数原型:
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
参数说明:
pthread_t *thread: 指向pthread_t类型的指针,用于存储新创建线程的线程 ID。const pthread_attr_t *attr: 指向pthread_attr_t结构的指针,该结构包含了新线程的属性。如果不需要设置特定属性,可以传递NULL。void *(*start_routine)(void*): 新线程的入口函数(回调函数应用),即线程开始执行时调用的函数。这个函数必须有返回值类型void*并接受一个void*类型的参数。void *arg: 传递给(回调函数)线程入口函数start_routine的参数。
个人理解补充:
- 该函数的第一个参数 pthread_t *thread:传递一个 pthread_t 类型的指针变量,也可以直接传递某个 pthread_t 类型变量的地址。**pthread_t 是一种用于表示线程的数据类型,每一个 pthread_t 类型的变量都可以表示一个线程。**例如 int 是一种表示整数的数据类型,每个 int 类型的变量都可以表示一个整数,它们都是数据类型的一种。
- 该函数的第三个参数是正在创建的该线程需要执行的函数,需要注意的是这里是以函数指针的方式指明新建线程需要执行的函数,该函数的形参和返回值都必须为
void*类型。void*类型又称空指针类型,表明指针所指数据的类型是未知的(如果不理解,类比于结构体指针一样理解)。使用此类型指针时,我们通常需要先对其进行强制类型转换,然后才能正常访问指针指向的数据。- 返回值:如果成功创建线程,
pthread_create()函数返回数字 0,反之返回非零值。各个非零值都对应着不同的宏,指明创建失败的原因,这里可以自己去了解。
void* thread_function(void *arg) {
// 线程要执行的代码
return NULL;
}
int main() {
pthread_t thread_id;
int result = pthread_create(&thread_id, NULL, thread_function, NULL);
if (result) {
// 错误处理
}
// 其他代码
return 0;
}
3.pthread_exit函数
用于终止调用线程。在 C 语言中,该函数的原型定义在 <pthread.h> 头文件中。函数原型如下:
void pthread_exit(void *retval);
参数说明:
retval:指向一个指针的指针,用于返回一个值给其他线程。如果调用线程不关心返回值,可以传递NULL。
当线程调用 pthread_exit 时,它将立即终止。如果有其他线程使用 pthread_join 等待该线程的结束,并且提供了一个接收返回值的指针,那么这个返回值将会被存储在提供的指针所指向的位置。
需要注意的是,pthread_exit 并不会清理线程的栈,也不会执行任何线程的清理处理程序。如果需要执行清理操作,应该使用 pthread_cleanup_push 和 pthread_cleanup_pop 函数来注册和注销清理函数。
该函数用来终止线程执行。多线程程序中,终止线程执行的方式本来有 3 种,分别是:
-
线程执行完成后,自行终止;
-
线程执行过程中遇到了 pthread_exit() 或者 return,也会终止执行;
-
线程执行过程中,接收到其它线程发送的“终止执行”的信号,然后终止执行。
第一种的理解就是什么也不管,线程执行完会自己终止;第二种就是本部分要用的pthread_exit()函数,return也好理解,返回即终止;第三种方法,本来要使用pthread_cancel()函数,但在使用这个函数时会出现其他一系列的问题,解决起来非常麻烦,所以除非特殊情况,我们一般使用第二种方式。
补充:pthread_exit()和return的区别:首先,return 语句和 pthread_exit() 函数的含义不同,return 的含义是返回,它不仅可以用于线程执行的函数,普通函数也可以使用;pthread_exit() 函数的含义是线程退出,它专门用于结束某个线程的执行。实际使用中,我们终止子线程一般都使用pthread_exit()函数,不建议使用return。
4.pthread_join函数
pthread_join 函数是 POSIX 线程库中的一个函数,用于等待一个线程结束。这个函数允许一个线程(通常称为“主线程”)等待另一个线程(即被等待的线程)完成执行。在 C 语言中,该函数的原型定义在 <pthread.h> 头文件中。函数原型如下:
int pthread_join(pthread_t thread, void **retval);
参数说明:
thread:标识要等待的线程的线程 ID。retval:一个指向指针的指针,用于存储被等待线程的返回值。如果不需要获取返回值,可以传递NULL。
返回值:
- 成功时,
pthread_join函数返回 0。 - 失败时,返回一个错误编号,具体错误编号可以在
<errno.h>头文件中找到。
当 pthread_join 被调用时,如果 thread 参数指定的线程已经结束,那么 pthread_join 会立即返回。如果指定的线程还没有结束,调用 pthread_join 的线程将阻塞,直到被等待的线程退出。一旦被等待的线程退出,retval 指向的变量将被设置为该线程的返回值(如果有的话)。
5.pthread_cancel函数
pthread_cancel 函数是 POSIX 线程(pthread)库中的一个函数,用于请求取消(终止)一个指定的线程。当调用这个函数时,目标线程将收到一个取消信号,并且通常会执行一些清理操作后终止。这个函数的原型定义在 <pthread.h> 头文件中,如下所示:
int pthread_cancel(pthread_t thread);
参数说明:
thread:需要被取消的线程的标识符。
返回值:
- 成功时,返回 0。
- 失败时,返回一个错误编号,具体错误编号可以在
<errno.h>头文件中找到。
需要注意的是,pthread_cancel 函数只是发起了一个取消请求,并不保证目标线程会立即停止执行。目标线程可能会在完成当前操作或者达到一个安全点后才开始处理取消请求。此外,目标线程可以安装取消处理函数(使用 pthread_setcanceltype 和 pthread_cleanup_push/pthread_cleanup_pop),以便在被取消时执行一些清理工作。
6.pthread_detach函数
pthread_detach 函数用于将一个线程标记为分离状态(detached)。当线程被创建时,默认情况下它是“joinable”,这意味着在它结束执行之前,其他线程可以使用 pthread_join 函数来等待它结束。如果一个线程被标记为分离状态,那么一旦线程结束执行,它的资源将自动被释放,不需要其他线程显式调用 pthread_join。
pthread_detach 函数的原型定义在 <pthread.h> 头文件中,如下所示:
int pthread_detach(pthread_t thread);
参数说明:
thread:需要被标记为分离状态的线程的标识符。
返回值:
- 成功时,返回 0。
- 失败时,返回一个错误编号,具体错误编号可以在
<errno.h>头文件中找到。
三.线程同步
1.缘由
多线程程序中各个线程除了可以使用自己的私有资源(局部变量、函数形参等)外,还可以共享全局变量、静态变量、堆内存、打开的文件等资源。我们通常将“多个线程同时访问某一公共资源”的现象称为“线程间产生了资源竞争”或者“线程间抢夺公共资源”,线程间竞争资源往往会导致程序的运行结果出现异常,我们常常采用同步机制来解决这种问题。
2.实现方法
实现线程同步的常用方法有 4 种,分别称为互斥锁、信号量、条件变量和读写锁。
-
互斥锁(
Mutex)又称互斥量或者互斥体,是最简单也最有效地一种线程同步机制。互斥锁的用法和实际生活中的锁非常类似,当一个线程访问公共资源时,会及时地“锁上”该资源,阻止其它线程访问;访问结束后再进行“解锁”操作,将该资源让给其它线程访问。 -
信号量又称“信号灯”,主要用于控制同时访问公共资源的线程数量,当线程数量控制在 ≤1 时,该信号量又称二元信号量,功能和互斥锁非常类似;当线程数量控制在 N(≥2)个时,该信号量又称多元信号量,指的是同一时刻最多只能有 N 个线程访问该资源。
-
条件变量的功能类似于实际生活中的门,门有“打开”和“关闭”两种状态,分别对应条件变量的“成立”状态和“不成立”状态。当条件变量“不成立”时,任何线程都无法访问资源,只能等待条件变量成立;一旦条件变量成立,所有等待的线程都会恢复执行,访问目标资源。为了防止各个线程竞争资源,条件变量总是和互斥锁搭配使用。
-
多线程程序中,如果大多数线程都是对公共资源执行读取操作,仅有少量的线程对公共资源进行修改,这种情况下可以使用读写锁解决线程同步问题。
这里我们使用最简单的也是最常用的方法:互斥锁。
6158

被折叠的 条评论
为什么被折叠?



