在餐厅场景中,进程好比不同部门,像厨房、收银台、服务员团队。各部门有独立工作空间与资源,操作互不影响,且占用不同资源,部门间通信有成本。线程则类似部门内员工,如厨房的厨师,他们共享部门资源,分工协作执行任务,而且创建和销毁一个员工(线程)的开销 ,比开设新部门(进程)要小得多。

线程

在 Linux 系统中,进程和线程都是实现程序并发执行的方式,但它们之间存在一些关键的区别,这些区别决定了为什么即使有了进程,仍然需要线程:

  • 资源开销

    • 进程拥有独立的地址空间,这意味着每个进程都有自己的一套数据段、堆栈段和代码段,这导致创建和销毁进程的开销较大。

    • 线程共享同一进程的地址空间,包括内存资源和文件描述符等,因此创建和销毁线程的开销较小。

  • 通信效率

    • 进程间通信(IPC)需要通过特定的机制如管道、消息队列、共享内存等来实现,这增加了通信的复杂性和开销。

    • 同一进程内的线程可以直接访问共享的内存区域,使得线程间的通信更加高效和简单。

  • 调度灵活性

    • 操作系统调度的基本单位是进程,这意味着如果一个进程中的任务需要等待 I/O 操作完成,整个进程都会被阻塞。

    • 线程作为更细粒度的调度单位,可以在一个线程等待 I/O 操作时,让其他线程继续执行,提高了系统的响应速度和资源利用率。

多线程在项目中的应用场景是怎么样的呢?我们举个例子

例如我们的页面时间、或者状态需要进行定时刷新,那我们就把UI作为一个前端线程,然后单独开一个后端线程查询时间或状态,一旦时间或状态更新了就通知前端的UI线程进行刷新,这样就可以实现页面的不阻塞异步更新。

Linux的线程库是后来加入的,内核没有它的库,使用时得记得链接线程库

target_link_libraries(project_name pthread)

在 Linux 中,使用 C 语言进行多线程编程时,pthread 库提供了几个核心函数来管理线程的创建、退出、等待和取消。以下是这些函数的详细说明:

pthread_create

#include <pthread.h>
pthread_create
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
功能:创建一个新的线程。
参数:
thread:用于存储新创建线程的标识符。
attr:用于指定线程属性(如栈大小、优先级等)。通常设置为 NULL 表示使用默认属性。
start_routine:线程的入口函数,类型为 void *(*start_routine)(void *)。
arg:传递给 start_routine 函数的参数,类型为 void *。
返回值:成功时返回 0,失败时返回错误码。

pthread_join

int pthread_join(pthread_t thread, void **retval);
功能:等待指定的线程结束,并获取其返回值。
参数:
thread:要等待的线程的标识符。
retval:指向 void * 类型的指针,用于存储线程的返回值。如果不需要获取返回值,可以设置为 NULL。
返回值:成功时返回 0,失败时返回错误码。

pthread_exit

void pthread_exit(void *retval);
功能:使当前线程终止,并返回一个值。
参数:
retval:线程的返回值,类型为 void *。这个值可以通过 pthread_join 函数获取。
返回值:无返回值,因为该函数不会返回。

pthread_cancel

int pthread_cancel(pthread_t thread);
功能:请求取消指定的线程。可以用于一个线程取消另一个线程
参数:
thread:要取消的线程的标识符。
返回值:成功时返回 0,失败时返回错误码。

例如线程在正常完成后,返回一个值

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void *thread_function(void *arg) {
    int *data = (int *)arg;
    printf("Thread: Data received is %d\n", *data);
    pthread_exit((void *)123); // 线程返回值
}

int main() {
    pthread_t thread;
    int data = 42;

    if (pthread_create(&thread, NULL, thread_function, (void *)&data) != 0) {
        printf("pthread_create fail");
        return 1;
    }

    void *retval;
    if (pthread_join(thread, &retval) != 0) {
        printf("pthread_join fail");
        return 1;
    }
    printf("Main: Thread returned with value %ld\n", (long)retval);

    return 0;
}

线程互斥锁

在 Linux 中,使用 C 语言进行多线程编程时,互斥锁(Mutex)是一种常用的同步机制,用于保护共享资源,防止多个线程同时访问同一资源而导致的数据不一致问题。互斥锁的主要功能是确保同一时间只有一个线程可以访问临界区(即需要保护的代码段)。

pthread_mutex_init

初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
功能:初始化一个互斥锁。
参数:
mutex:指向 pthread_mutex_t 类型的指针,用于存储互斥锁。
attr:指向 pthread_mutexattr_t 类型的指针,用于指定互斥锁属性。通常设置为 NULL 表示使用默认属性。
返回值:成功时返回 0,失败时返回错误码。

pthread_mutex_lock

获取互斥锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:获取互斥锁。如果锁已被其他线程占用,调用线程会阻塞,直到锁被释放。
参数:
mutex:指向 pthread_mutex_t 类型的指针,用于获取互斥锁。
返回值:成功时返回 0,失败时返回错误码。

 pthread_mutex_trylock

尝试获取互斥锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
功能:尝试获取互斥锁,如果锁已被其他线程持有,则立即返回。
参数:
mutex:指向 pthread_mutex_t 类型的指针,用于尝试获取互斥锁。
返回值:
成功时返回 0。
如果锁已被其他线程持有,返回 EBUSY。
其他错误返回相应的错误码。

pthread_mutex_unlock

释放互斥锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:释放互斥锁。
参数:
mutex:指向 pthread_mutex_t 类型的指针,用于释放互斥锁。
返回值:成功时返回 0,失败时返回错误码。

pthread_mutex_destroy

销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:销毁一个互斥锁。
参数:
mutex:指向 pthread_mutex_t 类型的指针,用于销毁互斥锁。
返回值:成功时返回 0,失败时返回错误码。

示例代码

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

// 共享资源
int shared_data = 0;

// 互斥锁
pthread_mutex_t mutex;

// 线程入口函数
void *thread1_function(void *arg) {
    for (int i = 0; i < 1000; i++) {
        // 获取互斥锁
        pthread_mutex_lock(&mutex);
        // 临界区
        shared_data++;
        printf("Thread 1: shared_data = %d\n", shared_data);
        // 释放互斥锁
        pthread_mutex_unlock(&mutex);
        // 模拟一些工作
        usleep(1);
    }
    pthread_exit(NULL);
}

void *thread2_function(void *arg) {
    for (int i = 0; i < 1000; i++) {
        pthread_mutex_lock(&mutex);
        shared_data++;
        printf("Thread 2: shared_data = %d\n", shared_data);
        pthread_mutex_unlock(&mutex);
        usleep(1);
    }
    pthread_exit(NULL);
}

int main() {
    pthread_t thread1, thread2;
    // 初始化互斥锁
    if (pthread_mutex_init(&mutex, NULL) != 0) {
        printf("pthread_mutex_init fail\n");
        return 1;
    }

    // 创建线程
    if (pthread_create(&thread1, NULL, thread1_function, NULL) != 0) {
        printf("pthread_create fail\n");
        return 1;
    }
    if (pthread_create(&thread2, NULL, thread2_function, NULL) != 0) {
        printf("pthread_create fail\n");
        return 1;
    }

    // 等待线程结束
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    // 销毁互斥锁
    if (pthread_mutex_destroy(&mutex) != 0) {
        printf("pthread_mutex_destroy fail\n");
        return 1;
    }

    return 0;
}

需要注意的是互斥锁(Mutex)主要用于多线程环境中的同步,但在多进程环境中使用互斥锁需要特别注意。标准的 POSIX 互斥锁(pthread_mutex_t)是线程私有的,不能直接用于多进程之间的同步。这是因为每个进程都有独立的地址空间,互斥锁的状态信息(如锁的持有者、锁的状态等)存储在进程的内存中,无法在不同的进程之间共享。

如果需要在多个进程之间共享互斥锁,必须使用 PTHREAD_PROCESS_SHARED 属性,并且互斥锁需要存储在共享内存中,这种方式过于冗杂。实际使用时,还是建议使用信号量进行资源多进程资源管理。下面是进程的案例:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sys/wait.h>

#define SHM_NAME "/my_shared_memory"
#define SHM_SIZE 1024

int main() {
    int *shared_data;
    pthread_mutex_t *mutex;
    int fd;

    // 创建共享内存
    fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
    if (fd == -1) {
        printf("shm_open fail");
        exit(1);
    }

    // 设置共享内存大小
    if (ftruncate(fd, SHM_SIZE) == -1) {
        printf("ftruncate fail");
        exit(1);
    }

    // 映射共享内存
    void *addr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) {
        printf("mmap fail");
        exit(1);
    }

    // 初始化共享数据和互斥锁
    shared_data = (int *)addr;
    mutex = (pthread_mutex_t *)(addr + sizeof(int));
    *shared_data = 0;

    // 设置互斥锁属性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);

    // 初始化互斥锁
    if (pthread_mutex_init(mutex, &attr) != 0) {
        printf("pthread_mutex_init fail");
        exit(1);
    }

    // 创建子进程
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程
        for (int i = 0; i < 1000; i++) {
            // 获取互斥锁
            pthread_mutex_lock(mutex);
            (*shared_data)++;
            printf("Child: shared_data = %d\n", *shared_data);
            // 释放互斥锁
            pthread_mutex_unlock(mutex);
            usleep(1);
        }
        exit(1);
    } else {
        // 父进程
        for (int i = 0; i < 1000; i++) {
            pthread_mutex_lock(mutex);
            (*shared_data)++;
            printf("Parent: shared_data = %d\n", *shared_data);
            pthread_mutex_unlock(mutex);
            usleep(1);
        }

        // 等待子进程结束
        wait(NULL);

        // 清理资源
        pthread_mutex_destroy(mutex);
        munmap(addr, SHM_SIZE);
        close(fd);
        shm_unlink(SHM_NAME);
    }

    return 0;
}

线程同步

在 Linux 中,C 语言提供了条件变量(Condition Variables)来实现线程间的同步。条件变量通常与互斥锁(Mutex)结合使用,以实现更复杂的同步逻辑。

例程中,我们创建了两个线程,一个线程作为生产者生成数据,另一个线程作为消费者消耗数据,生产者每3s生产一条数据,而消费者每1s可以消费一条数据。在没有条件变量的情况下,消费者就需要一直轮询才能知道有没有新数据产生,这样的行为无意义且消耗资源。

如何解决呢,我们使用条件变量函数:

Condition Varialbes

初始化条件变量Condition Varialbes
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
功能:初始化一个条件变量。
参数:
cond:指向 pthread_cond_t 类型的指针,用于存储条件变量。
attr:指向 pthread_condattr_t 类型的指针,用于指定条件变量属性。通常设置为 NULL 表示使用默认属性。
返回值:成功时返回 0,失败时返回错误码。

pthread_cond_signal

唤醒一个等待的线程
int pthread_cond_signal(pthread_cond_t *cond);
功能:唤醒一个等待该条件变量的线程。
参数:
cond:指向 pthread_cond_t 类型的指针,表示要唤醒的条件变量。
返回值:成功时返回 0,失败时返回错误码。

pthread_cond_wait


等待条件变量
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
功能:等待条件变量。调用此函数的线程会释放互斥锁并进入等待状态,直到被其他线程唤醒。
参数:
cond:指向 pthread_cond_t 类型的指针,表示要等待的条件变量。
mutex:指向 pthread_mutex_t 类型的指针,表示与条件变量关联的互斥锁。
返回值:成功时返回 0,失败时返回错误码。

pthread_cond_destroy


销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
功能:销毁一个条件变量。
参数:
cond:指向 pthread_cond_t 类型的指针,用于销毁条件变量。
返回值:成功时返回 0,失败时返回错误码。

以下是一个简单的示例,展示了如何使用条件变量来实现生产者-消费者模型:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

// 共享资源
int shared_data = 0;

// 互斥锁
pthread_mutex_t mutex;
// 条件变量
pthread_cond_t cond;

// 生产者线程
void *producer(void *arg) {
    for (int i = 0; i < 5; i++) {
        // 获取互斥锁
        pthread_mutex_lock(&mutex);
        // 更新共享资源
        shared_data++;
        printf("Producer: shared_data = %d\n", shared_data);
        // 通知消费者
        pthread_cond_signal(&cond);
        // 释放互斥锁
        pthread_mutex_unlock(&mutex);
        // 模拟一些工作
        sleep(3);
    }
    pthread_exit(NULL);
}

// 消费者线程
void *consumer(void *arg) {
    for (int i = 0; i < 5; i++) {
        // 获取互斥锁
        pthread_mutex_lock(&mutex);
        // 等待条件满足
        while (shared_data == 0) {
            pthread_cond_wait(&cond, &mutex);
        }
        // 更新共享资源
        shared_data--;
        printf("Consumer: shared_data = %d\n", shared_data);
        // 释放互斥锁
        pthread_mutex_unlock(&mutex);
        // 模拟一些工作
        sleep(1);
    }
    pthread_exit(NULL);
}

int main() {
    pthread_t producer_thread, consumer_thread;

    // 初始化互斥锁
    if (pthread_mutex_init(&mutex, NULL) != 0) {
        printf("pthread_mutex_init fail");
        return 1;;
    }
    // 初始化条件变量
    if (pthread_cond_init(&cond, NULL) != 0) {
        printf("pthread_cond_init fail");
        return 1;;
    }

    // 创建生产者和消费者线程
    if (pthread_create(&producer_thread, NULL, producer, NULL) != 0) {
        printf("pthread_create fail");
        return 1;;
    }
    if (pthread_create(&consumer_thread, NULL, consumer, NULL) != 0) {
        printf("pthread_create fail");
        return 1;;
    }

    // 等待线程结束
    if (pthread_join(producer_thread, NULL) != 0) {
        printf("pthread_join fail");
        return 1;;
    }
    if (pthread_join(consumer_thread, NULL) != 0) {
        printf("pthread_join fail");
        return 1;;
    }

    // 销毁互斥锁和条件变量
    if (pthread_mutex_destroy(&mutex) != 0) {
        printf("pthread_mutex_destroy fail");
        return 1;;
    }
    if (pthread_cond_destroy(&cond) != 0) {
        printf("pthread_cond_destroy fail");
        return 1;;
    }

    return 0;
}
初始化互斥锁和条件变量:
使用 pthread_mutex_init 初始化互斥锁 mutex。
使用 pthread_cond_init 初始化条件变量 cond。
创建生产者和消费者线程:
使用 pthread_create 创建生产者线程 producer_thread 和消费者线程 consumer_thread。
生产者线程:
获取互斥锁 mutex。
更新共享资源 shared_data。
使用 pthread_cond_signal 通知消费者线程。
释放互斥锁 mutex。
模拟一些工作,使用 sleep 休眠 3 秒。
消费者线程:
获取互斥锁 mutex。
使用 pthread_cond_wait 等待条件满足(即 shared_data 不为 0)。
更新共享资源 shared_data。
释放互斥锁 mutex。
模拟一些工作,使用 sleep 休眠 1 秒。
等待线程结束:
使用 pthread_join 等待生产者和消费者线程结束。
销毁互斥锁和条件变量:
使用 pthread_mutex_destroy 销毁互斥锁 mutex。
使用 pthread_cond_destroy 销毁条件变量 cond。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐