本文最后更新于 2025-06-25,学习久了要注意休息哟

第三章 Linux 多任务开发—线程

3.1 线程相关命令

在Linux系统中,线程是轻量级的执行单元,与进程共享同一地址空间,是操作系统调度的最小单位。以下是关于线程的总结:

线程是轻量级的执行单元

线程是共享 整个 进程的资源
线程没有独立的内存空间 
进程开发需要使用 <pthread.h> 这个库 来进行开发 Linux内没有 线程的这个概念
并且 在编译的时候 需要连接 -lpthread    静态库 动态库

竞态  
静态
  1. 线程是进程的执行单元

  2. 线程是在进程内部创建和管理的,与同一进程的其他线程共享代码段(text)、数据段(data)、堆(heap)和共享库等资源。

  3. 资源共享

  4. 所有线程共享同一进程的资源,如内存空间,这使得线程间的数据共享更为方便。

  5. 没有独立的内存空间

  6. 不同于进程,线程不拥有独立的内存空间,它们使用的是同一进程的内存空间。

  7. 系统调度单位

  8. 线程是操作系统进行调度和执行的基本单位,可以实现并发执行,提高程序的效率。

  9. 编程和库支持

  10. 在Linux编程中,可以使用 <pthread.h> 头文件来支持线程的创建和管理。

  11. 在编译时,需要链接 -lpthread 库来确保线程相关的函数库被正确链接。

  12. 多线程的优势和注意事项

  13. 多线程可以提高程序的响应速度和效率,特别是在涉及到IO密集型任务和并行计算时。

  14. 线程间的共享数据需要进行同步操作,以避免竞争条件和数据不一致的问题。

查看线程的相关命令

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 互斥锁的使用

是一种用于保护共享数据结构不受并发修改的设备,目的是确保在同一时刻只有一个线程能够访问共享资源,从而实现临界区的保护和监视器的功能。

互斥锁有两种可能的状态:

  1. 解锁:互斥锁不属于任何线程,任何线程都可以尝试获取该锁 。
  2. 锁定:互斥锁属于一个线程,其他线程在尝试获取该锁时会被阻塞。

互斥锁的特性是:永远不能同时被两个线程持有。如果一个线程试图锁定一个已经被其他线程锁定的互斥量,该线程会被挂起,直到该互斥量被解锁为止。

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 防止死锁