04_系统编程-线程篇
本文最后更新于 2025-06-25,学习久了要注意休息哟
第三章 Linux 多任务开发—线程
3.1 线程相关命令
在Linux系统中,线程是轻量级的执行单元,与进程共享同一地址空间,是操作系统调度的最小单位。以下是关于线程的总结:
线程是轻量级的执行单元
线程是共享 整个 进程的资源
线程没有独立的内存空间
进程开发需要使用 <pthread.h> 这个库 来进行开发 Linux内没有 线程的这个概念
并且 在编译的时候 需要连接 -lpthread 静态库 动态库
竞态
静态
线程是进程的执行单元:
线程是在进程内部创建和管理的,与同一进程的其他线程共享代码段(text)、数据段(data)、堆(heap)和共享库等资源。
资源共享:
所有线程共享同一进程的资源,如内存空间,这使得线程间的数据共享更为方便。
没有独立的内存空间:
不同于进程,线程不拥有独立的内存空间,它们使用的是同一进程的内存空间。
系统调度单位:
线程是操作系统进行调度和执行的基本单位,可以实现并发执行,提高程序的效率。
编程和库支持:
在Linux编程中,可以使用
<pthread.h>
头文件来支持线程的创建和管理。在编译时,需要链接
-lpthread
库来确保线程相关的函数库被正确链接。多线程的优势和注意事项:
多线程可以提高程序的响应速度和效率,特别是在涉及到IO密集型任务和并行计算时。
线程间的共享数据需要进行同步操作,以避免竞争条件和数据不一致的问题。
查看线程的相关命令
ps -T [pid] pid 下的相关线程
3.2 线程的创建与管理
3.2.1 线程的创建与回收
pthread_create
函数
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
功能:
创建一个新的线程,并在新线程中执行指定的线程函数 start_routine。
参数:
thread: 用来保存新线程 ID 的缓冲区的首地址 TID
attr: 这是一个指向线程属性结构体的指针,pthread_attr_t 类型。此结构体用于设置线程的属性(如栈大小、调度策略等)。
如果为 NULL,则使用默认的线程属性。
start_routine 线程的函数
arg: 参数
返回值
成功 返回 0
失败 返回错误码
pthread_join
函数
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
功能:
1、在主线程中 等待 线程结束 会阻塞等待 直到目标结束
2、获取线程退出状态 通过 retval 来获取退出的状态 通过return 返回的值
参数:
thread:被等待的线程的标识符,即目标线程的 pthread_t 类型的标识符。
retval:一个指向指针的指针,用于存储被等待线程的退出状态。
返回值:
成功,返回0
错误,返回错误编号
pthread_exit
函数
#include <pthread.h>
void pthread_exit(void *retval);
功能:
pthread_exit 函数用于在线程函数内部结束线程的执行,并可选地返回一个指针 retval,作为线程的退出状态。
参数:
retval:一个 void * 类型的指针,表示线程的退出状态。可以传递任意类型的数据作为线程函数的返回值。
返回值:
无
3.2.2 示例代码-不传递参数
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 线程函数,不接受参数
void *thread_function(void *arg) {
printf("Thread started\n");
sleep(3); // 模拟线程执行一段时间
printf("Thread finished\n");
pthread_exit(NULL);
}
int main() {
pthread_t tid;
// 创建线程,不传递参数
if (pthread_create(&tid, NULL, thread_function, NULL) != 0) {
fprintf(stderr, "Failed to create thread\n");
return 1;
}
// 等待线程结束
pthread_join(tid, NULL);
printf("Main thread exiting\n");
return 0;
}
3.2.3 示例代码-传递参数
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 线程函数,接受整数参数
void *thread_function(void *arg) {
int *value = (int *)arg; // 将void指针转换为整数指针
printf("Thread received value: %d\n", *value);
sleep(3); // 模拟线程执行一段时间
printf("Thread finished\n");
pthread_exit(NULL);
}
int main() {
pthread_t tid;
int value = 42; // 动态空间
// 创建线程,并传递参数
if (pthread_create(&tid, NULL, thread_function, (void *)&value) != 0) {
fprintf(stderr, "Failed to create thread\n");
return 1;
}
// 等待线程结束
pthread_join(tid, NULL);
printf("Main thread exiting\n");
return 0;
}
3.2.4 示例代码-传递参数-结构体
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 定义一个结构体,用于传递多个参数
struct ThreadArgs {
int arg1;
float arg2;
char arg3;
};
// 线程函数,接受结构体参数
void *thread_function(void *arg) {
struct ThreadArgs *args = (struct ThreadArgs *)arg; // 将void指针转换为结构体指针
printf("Thread received args: %d, %.2f, %c\n", args->arg1, args->arg2, args->arg3);
sleep(3); // 模拟线程执行一段时间
printf("Thread finished\n");
pthread_exit(NULL);
}
int main() {
pthread_t tid;
struct ThreadArgs args = {10, 3.14, 'A'};
// 创建线程,并传递结构体参数
if (pthread_create(&tid, NULL, thread_function, (void *)&args) != 0) {
fprintf(stderr, "Failed to create thread\n");
return 1;
}
// 等待线程结束
pthread_join(tid, NULL);
printf("Main thread exiting\n");
return 0;
}
3.2.6 示例代码-返回值
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 线程函数,返回一个指针作为返回值
void *thread_function(void *arg) {
int *value = (int *)arg; // 将void指针转换为整数指针
printf("Thread received value: %d\n", *value);
sleep(3); // 模拟线程执行一段时间
*value = 2; // 修改传递进来的值
pthread_exit((void *)value); // 通过pthread_exit返回值
// 或者
}
int main() {
pthread_t tid;
int value = 42;
void *retval;
// 创建线程,并传递参数
if (pthread_create(&tid, NULL, thread_function, (void *)&value) != 0) {
fprintf(stderr, "Failed to create thread\n");
return 1;
}
// 等待线程结束,并获取返回值
if (pthread_join(tid, &retval) != 0) {
fprintf(stderr, "Failed to join thread\n");
return 1;
}
// 获取线程函数返回的值,并打印
int *result = (int *)retval;
printf("Main thread received value: %d\n", *result);
// 释放返回值的内存,这里不是必须的,因为返回的是指针,不是动态分配的内存
// free(result);
printf("Main thread exiting\n");
return 0;
}
3.2.3 示例代码-线程通讯
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#define MAX_COUNT 10
// 全局变量,用于线程通信
int counter = 0;
// 线程函数:增加计数
void *increase_counter(void *arg) {
for (int i = 0; i < MAX_COUNT; ++i) {
counter++; // 增加计数器的值
sleep(1); // 模拟计数过程中的其他操作
}
pthread_exit(NULL);
}
// 线程函数:显示计数
void *display_counter(void *arg) {
for (int i = 0; i < MAX_COUNT; ++i) {
printf("Current count: %d\n", counter); // 显示当前计数器的值
sleep(2); // 模拟显示过程中的其他操作
}
pthread_exit(NULL);
}
int main() {
pthread_t tid_increase, tid_display;
// 创建增加计数的线程
if (pthread_create(&tid_increase, NULL, increase_counter, NULL) != 0) {
fprintf(stderr, "pthread_create:\n");
return 1;
}
// 创建显示计数的线程
if (pthread_create(&tid_display, NULL, display_counter, NULL) != 0) {
fprintf(stderr, "pthread_create:\n");
return 1;
}
// 等待两个线程结束
if (pthread_join(tid_increase, NULL) != 0) {
fprintf(stderr, "pthread_join:\n");
return 1;
}
if (pthread_join(tid_display, NULL) != 0) {
fprintf(stderr, "pthread_join:\n");
return 1;
}
printf("Main thread exiting\n");
return 0;
}
3.3 线程的常用函数
3.3.1 分离态
线程的状态分为结合态和分离态:
- 结合态:默认情况下,线程是结合态的,线程结束后需要使用
pthread_join
进行资源回收,否则会造成资源泄漏。 - 分离态:如果线程被设置为分离态,线程结束时资源会自动由操作系统回收,无需使用
pthread_join
。
#include <pthread.h>
int pthread_detach(pthread_t thread);
功能:
使用 `pthread_detach` 可以将一个线程设置为分离态,避免手动回收线程资源。
参数:
pthread: 线程的ID
返回值:
成功 0
失败 错误码
3.3.2 终止线程
pthread_cancel
函数
#include <pthread.h>
int pthread_cancel(pthread_t thread);
功能:
发送取消请求以终止指定线程的执行。
参数:
thread: 需要取消的线程ID。
返回值:
成功: 返回 0。
失败: 返回错误码。
json
如何解json
pthread_setcancelstate
函数
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
功能:
设置当前线程的取消状态(启用或禁用取消功能),并返回之前的取消状态。
参数:
state: 新的取消状态,可以是以下值之一:
* `PTHREAD_CANCEL_ENABLE` - 允许线程被取消(默认)。
* `PTHREAD_CANCEL_DISABLE` - 禁止线程被取消。
oldstate: 用于存储之前的取消状态的指针。如果不需要保存之前的状态,可以传递 `NULL`。
返回值:
成功: 返回 0。
失败: 返回错误码。
示例
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void* thread_func(void* arg) {
int oldstate;
// 禁止线程被取消 DISABLE 失能
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
for (int i = 0; i < 5; i++) {
printf("Thread is running... (cancel disabled)\n");
sleep(1);
}
// 重新启用线程取消 ENABLE 使能
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate);
while (1) {
printf("Thread is running... (cancel enabled)\n");
sleep(1);
}
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
sleep(3); // 让线程运行3秒
pthread_cancel(tid); // 尝试取消线程
pthread_join(tid, NULL); // 等待线程结束
printf("Thread canceled\n");
return 0;
}
pthread_setcanceltype
函数
#include <pthread.h>
int pthread_setcanceltype(int type, int *oldtype);
功能:
设置线程取消的类型(即取消请求何时生效),并返回之前的取消类型。
参数:
type: 新的取消类型,可以是以下值之一:
* `PTHREAD_CANCEL_DEFERRED` - 取消操作被推迟到线程到达一个取消点时才生效(默认)。
* `PTHREAD_CANCEL_ASYNCHRONOUS` - 取消操作立即生效。
oldtype: 用于存储之前的取消类型的指针。如果不需要保存之前的类型,可以传递 `NULL`。
返回值:
成功: 返回 0。
失败: 返回错误码。
第四章 线程的同步互斥
4.1 互斥锁的使用
是一种用于保护共享数据结构不受并发修改的设备,目的是确保在同一时刻只有一个线程能够访问共享资源,从而实现临界区的保护和监视器的功能。
互斥锁有两种可能的状态:
- 解锁:互斥锁不属于任何线程,任何线程都可以尝试获取该锁 。
- 锁定:互斥锁属于一个线程,其他线程在尝试获取该锁时会被阻塞。
互斥锁的特性是:永远不能同时被两个线程持有。如果一个线程试图锁定一个已经被其他线程锁定的互斥量,该线程会被挂起,直到该互斥量被解锁为止。
4.1.1 相关函数
互斥锁的初始化和销毁
- pthread_mutex_init
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
功能:
初始化互斥锁。
参数:
mutex: 指向互斥锁对象的指针。
attr: 互斥锁属性,通常传递 `NULL` 表示使用默认属性。
返回值:
成功: 返回 0。
失败: 返回错误码。
- pthread_mutex_destroy
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:
销毁互斥锁,释放相关资源。
参数:
mutex: 指向要销毁的互斥锁对象的指针。
返回值:
成功: 返回 0。
失败: 返回错误码。
互斥锁的加锁和解锁
- pthread_mutex_lock(带阻塞)
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:
对互斥锁加锁。如果锁已被其他线程占用,则调用线程将阻塞,直到获得锁。
参数:
mutex: 指向要加锁的互斥锁对象的指针。
返回值:
成功: 返回 0。
失败: 返回错误码。
- pthread_mutex_trylock(不带阻塞)
#include <pthread.h>
int pthread_mutex_trylock(pthread_mutex_t *mutex);
功能:
尝试对互斥锁加锁。如果锁已被其他线程占用,`pthread_mutex_trylock` 会立即返回,而不是阻塞。
参数:
mutex: 指向要加锁的互斥锁对象的指针。
返回值:
成功: 返回 0。
失败: 返回 `EBUSY` 表示锁已被占用。
- pthread_mutex_unlock
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:
解锁互斥锁,使其他阻塞等待该锁的线程可以获得锁。
参数:
mutex: 指向要解锁的互斥锁对象的指针。
返回值:
成功: 返回 0。
失败: 返回错误码。
4.1.2 操作流程
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
功能:动态初始化互斥锁
参数:
mutex
mutexattr
1、定义互斥锁
pthread_mutex_t lock; // 定义互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 初始化静态互斥锁
2、初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutexattr_t *mutexattr);
功能: 动态初始化互斥锁
参数:
mutex 要初始化的锁
mutexattr 属性 一般传 NULL
返回值
成功 0
失败 错误码
3、上锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:上锁 如果没有锁可以被上锁,线程被阻塞
参数:mutex 互斥锁指针
返回值:
成功 0
失败 错误码
int pthread_mutex_trylock(pthread_mutex_t *mutex);
功能:上锁 如果没有锁可以被上锁,线程不会被阻塞
参数:mutex 互斥锁指针
返回值:
成功 0
失败 错误码
4、解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:解锁(一般使用时 只有加锁的线程可以解锁)
参数:互斥锁指针
返回值
成功 0
失败 错误码
5、销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:解锁在不需要使用互斥锁的时候销毁互斥锁
参数:mutex 互斥锁指针
返回值:
成功 0
失败 错误码
4.1.3 示例代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
// 定义互斥锁
pthread_mutex_t lock;
// 共享资源
int counter = 0;
// 线程函数
void* increment(void* arg) {
for (int i = 0; i < 100000; i++) {
pthread_mutex_lock(&lock); // 加锁
counter++; // 修改共享资源
pthread_mutex_unlock(&lock); // 解锁
}
return NULL;
}
int main() {
// 初始化互斥锁
if (pthread_mutex_init(&lock, NULL) != 0) {
printf("Mutex init failed\n");
return 1;
}
pthread_t threads[2];
// 创建两个线程
pthread_create(&threads[0], NULL, increment, NULL);
pthread_create(&threads[1], NULL, increment, NULL);
// 等待两个线程完成
pthread_join(threads[0], NULL);
pthread_join(threads[1], NULL);
// 输出计数器的最终值
printf("Final counter value: %d\n", counter);
// 销毁互斥锁
pthread_mutex_destroy(&lock);
return 0;
}
4.2 无名信号量
在 Linux 系统编程中,信号量是一种常见的同步机制,通常用于控制多个线程或进程对共享资源的访问。信号量有两种类型:无名信号量和命名信号量。无名信号量通常用于线程间的同步,特别是在同一个进程内部。它不需要在文件系统中创建,只存在于内存中,因此使用起来更加轻量和高效。
信号灯
4.2.1 相关函数
sem_init
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:
初始化一个无名信号量。
参数:
sem: 指向信号量的指针。
pshared: 设置为 0 时,信号量用于线程间同步;设置为非 0 时,信号量用于进程间同步。
value: 信号量的初始值。
返回值:
成功: 返回 0。
失败: 返回 -1,并设置相应的错误码。
sem_wait
P操作
#include <semaphore.h>
int sem_wait(sem_t *sem);
功能:
对信号量进行 P 操作,等待信号量大于 0 并对其减 1。
while(!sem)
sem == 0 死循环状态
sem == 1 跳出循环
参数:
sem: 指向信号量的指针。
返回值:
成功: 返回 0。
失败: 返回 -1,并设置相应的错误码。
sem_post
V操作
#include <semaphore.h>
int sem_post(sem_t *sem);
功能:
对信号量进行 V 操作,增加信号量的值。
sem++
参数:
sem: 指向信号量的指针。
返回值:
成功: 返回 0。
失败: 返回 -1,并设置相应的错误码。
sem_destroy
销毁无名信号量
#include <semaphore.h>
int sem_destroy(sem_t *sem);
功能:
销毁一个无名信号量,释放相关资源。
参数:
sem: 指向信号量的指针。
返回值:
成功: 返回 0。
失败: 返回 -1,并设置相应的错误码。
4.2.2 示例代码
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
sem_t sem;
void* thread_func(void* arg) {
printf("子线程等待信号量...\n");
sem_wait(&sem); // P 操作,等待信号量
printf("子线程获得信号量,继续执行...\n");
return NULL;
}
int main() {
pthread_t thread;
// 初始化信号量,初始值为 0,信号量用于线程间同步
sem_init(&sem, 0, 0);
// 创建子线程
pthread_create(&thread, NULL, thread_func, NULL);
// 主线程暂停一会儿,以确保子线程进入等待状态
sleep(1);
printf("主线程释放信号量...\n");
sem_post(&sem); // V 操作,增加信号量
// 等待子线程完成
pthread_join(thread, NULL);
// 销毁信号量
sem_destroy(&sem);
printf("程序结束\n");
return 0;
}
4.3 命名信号量
4.4 条件变量
条件变量是另一种同步机制,通常与互斥锁一起使用,帮助线程等待特定条件的发生。条件变量允许一个或多个线程等待某个条件,而另一个线程在条件满足时发出信号,通知等待的线程继续执行。条件变量的核心是 pthread_cond_t
以及相关的函数。
4.4.1 相关函数
pthread_cond_init
函数
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
功能:
初始化条件变量。
参数:
cond: 指向条件变量的指针。
attr: 条件变量的属性,一般设置为 NULL 使用默认属性。
返回值:
成功: 返回 0。
失败: 返回非 0 错误码。
pthread_cond_destroy
函数
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
功能:
销毁条件变量,释放相关资源。
参数:
cond: 指向条件变量的指针。
返回值:
成功: 返回 0。
失败: 返回非 0 错误码。
pthread_cond_wait
函数
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
功能:
等待条件变量满足,通常与互斥锁配合使用。在调用该函数时,线程将释放互斥锁并进入等待状态,直到其他线程发出信号唤醒它。被唤醒后,线程会重新获得互斥锁并继续执行。
参数:
cond: 指向条件变量的指针。
mutex: 指向互斥锁的指针。
返回值:
成功: 返回 0。
失败: 返回非 0 错误码。
pthread_cond_signal
函数
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
功能:
唤醒等待在条件变量上的一个线程。如果有多个线程在等待,则唤醒其中一个线程。
参数:
cond: 指向条件变量的指针。
返回值:
成功: 返回 0。
失败: 返回非 0 错误码。
pthread_cond_broadcast
函数
#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);
功能:
唤醒等待在条件变量上的所有线程。
参数:
cond: 指向条件变量的指针。
返回值:
成功: 返回 0。
失败: 返回非 0 错误码。
4.4.2 示例代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 共享数据和同步对象
int data_ready = 0; // 标记数据是否准备好
pthread_mutex_t mutex; // 互斥锁,保护共享数据
pthread_cond_t cond; // 条件变量,用于同步生产者和消费者
// 生产者线程函数
void* producer(void* arg) {
while (1) {
// 生产者做一些工作,比如生成数据
sleep(1); // 模拟生产过程
printf("生产者: 数据已生产!\n");
// 锁定互斥锁,准备更新共享数据
pthread_mutex_lock(&mutex);
data_ready = 1; // 标记数据已准备好
pthread_cond_signal(&cond); // 通知消费者数据已准备好
pthread_mutex_unlock(&mutex); // 解锁互斥锁
}
return NULL;
}
// 消费者线程函数
void* consumer(void* arg) {
while (1) {
// 锁定互斥锁,准备检查共享数据
pthread_mutex_lock(&mutex);
// 等待数据准备好
while (data_ready == 0) {
pthread_cond_wait(&cond, &mutex); // 解锁互斥锁并等待条件变量
}
// 一旦数据准备好,消费者可以消费数据
printf("消费者: 数据已消费!\n");
data_ready = 0; // 重置数据标记
pthread_mutex_unlock(&mutex); // 解锁互斥锁
}
return NULL;
}
int main() {
pthread_t prod_thread, cons_thread;
// 初始化互斥锁和条件变量
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
// 创建生产者和消费者线程
pthread_create(&prod_thread, NULL, producer, NULL);
pthread_create(&cons_thread, NULL, consumer, NULL);
// 等待线程结束(不会结束,因为是无限循环)
pthread_join(prod_thread, NULL);
pthread_join(cons_thread, NULL);
// 销毁互斥锁和条件变量
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
4.5 防止死锁
- 感谢你赐予我前进的力量