linux c语言进阶 - 线程,通信方式,安全方式(多并发)

线程

1. 概念

线程是程序中执行的最小单位,它属于进程的一部分。每个进程至少包含一个线程(主线程)。线程共享进程的资源(如内存空间、文件描述符等),但每个线程有自己的堆栈、程序计数器等。多线程编程允许程序在同一时间并行执行多个任务。

1.1 线程与进程的区别

  • 进程操作系统进行资源分配和调度的基本单位,每个进程有独立的内存空间(4G)。

  • 线程程序执行的基本单位,是在进程内运行的独立执行流。线程之间共享进程的内存空间、打开的文件、信号量等资源。

1.2 线程的作用

  1. 并发执行:在多核cpu的下,可以通知执行多个线程,效率高。 比如16核,那可以通过调度16个线程(多个进程)

  2. 异步执行: 不用同步执行。**等会写代码来测试。**

2. 线程的使用

2.1 线程的创建

pthread_create() 函数用于创建一个线程。

int pthread_create(pthread_t *tip, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

参数:

  1. thread : 线程 ID 变量指针

  2. attr : 线程属性,默认属性可设置为 NULL

  3. start_routine : 线程执行函数

  4. arg : 线程执行函数的参数

返回值:

成功 : 返回 0

失败 : 返回 错误码

注意:

一旦子线程创建成功,则会被独立调度执行 ,并且与其他线程 并发执行

创建多个线程时,⼀般由主线程统⼀创建,并等待释放资源或者分离线程, 不要递归创建。

在编译时需要链接 -lpthread

task.json(要修改编译语句)

javascript 复制代码
     "args": [
                "-fdiagnostics-color=always",
                "-g",
                "${file}",
                "-o",
                "${fileDirname}/${fileBasenameNoExtension}",
                "-lpthread"
            ],
  • pthread_t 是线程的标识符。

  • thread_function 是线程的执行函数。

  • pthread_join() 用于等待线程的结束。

int pthread_join(pthread_t thread, void **retval);

参数

thread : 线程 ID

retval : 获取线程退出值的指针

返回值

成功 : 返回 0

失败 : 返回 错误码

注意

线程分离函数不会阻塞线程的执行

  • pthread_exit()退出

void pthread_exit(void *retval);

参数:

retval : 线程返回值,通过指针传递

返回值:

成功 :返回 0

失败 : 返回 -1

注意:

1.当主线程调用 pthread_exit 函数时,进程不会结束,也不会导致其他子线程退出
2.任何线程调用 exit 函数会让进程结束

3.在线程函数内部显式调用 `pthread_exit()退出线程。
4.可以用于线程任意位置退出(不一定要在函数末尾)。

cpp 复制代码
#include <stdio.h>
#include <pthread.h>

void *demo(void *arg)
{
    printf("子线程!\n");
    return NULL;
}

/*线程*/
int main(int argc, char const *argv[])
{
    // 创建一个
    pthread_t tid;

    pthread_create(&tid, NULL, demo, NULL);

    pthread_join(tid, NULL);

    return 0;
}

2.2 线程的生命周期

线程的生命周期分为以下几个阶段:

  1. 创建阶段 :调用 pthread_create() 创建线程。

  2. 执行阶段:线程开始执行它的任务,执行完线程函数后线程自动终止。

  3. 终止阶段 :线程执行完成后会通过 pthread_exit() 或返回函数退出,终止其生命周期。

  4. 等待阶段 :线程通过 pthread_join() 被主线程或其他线程等待直到完成。

  5. 清理阶段:线程退出时会自动进行清理,释放资源。

2.3 线程的销毁

  • 自动销毁:线程结束时,系统会回收资源。

  • 手动销毁 :使用 pthread_cancel() 可以取消线程,但这需要特别小心,避免线程处于不确定状态。

3. 线程间的通讯(信号,信道,消息队列,共享内存,信号量)

linux c语言进阶 - 进程,通信方式-CSDN博客此文章介绍了一些进程的相关方法,需要的同志可以看一下。

3.1 共享内存

由于线程共享进程的内存空间,它们可以直接通过共享内存进行通信。不同线程可以修改共享变量或数据结构。但这也引发了 线程安全 的问题。

3.2 条件变量(Condition Variable)

条件变量是一种线程同步机制,用于在某个条件满足时唤醒线程。条件变量常与互斥锁一起使用。

cpp 复制代码
#include <pthread.h>
#include <stdio.h>

pthread_mutex_t mutex;
pthread_cond_t cond;

void *thread_function(void *arg) {
    pthread_mutex_lock(&mutex);
    pthread_cond_wait(&cond, &mutex); // 等待条件变量
    printf("Condition met, thread proceeding\n");
    pthread_mutex_unlock(&mutex);
    return NULL;
}

int main() {
    pthread_t thread_id;
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);
    
    pthread_create(&thread_id, NULL, thread_function, NULL);
    
    // 模拟条件满足
    pthread_mutex_lock(&mutex);
    pthread_cond_signal(&cond); // 唤醒等待的线程
    pthread_mutex_unlock(&mutex);
    
    pthread_join(thread_id, NULL);
    
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    return 0;
}

3.3 信号量(Semaphore)

信号量是另一种线程同步机制,通常用于控制多个线程访问共享资源的数量。(有名无名)

cpp 复制代码
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

#define NUM_THREADS 4
#define TOTAL_TICKETS 1000

int ticket_count = TOTAL_TICKETS;
sem_t semaphore;

void* sell_tickets(void* arg) {
    while (1) {
        sem_wait(&semaphore); // 获取信号量
        
        if (ticket_count <= 0) {
            sem_post(&semaphore); // 释放信号量
            break;
        }
        
        // 模拟售票处理时间
        struct timespec sleep_time = {0, 1000000}; // 1毫秒
        nanosleep(&sleep_time, NULL);
        
        printf("线程%ld卖出第%d张票\n", (long)arg, ticket_count);
        ticket_count--;
        
        sem_post(&semaphore); // 释放信号量
    }
    return NULL;
}

int main() {
    pthread_t threads[NUM_THREADS];
    sem_init(&semaphore, 0, 1); // 初始值为1的二进制信号量
    
    // 创建售票线程
    for (long i = 0; i < NUM_THREADS; i++) {
        pthread_create(&threads[i], NULL, sell_tickets, (void*)i);
    }
    
    // 等待所有线程完成
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }
    
    sem_destroy(&semaphore);
    printf("所有票已售罄\n");
    return 0;
}

3.4 互斥锁(Mutex)

互斥锁用于保护共享资源,避免多个线程同时访问共享资源,从而导致数据不一致。

cpp 复制代码
#include <stdio.h>
#include <pthread.h>

#define NUM_THREADS 4
#define TOTAL_TICKETS 1000

int ticket_count = TOTAL_TICKETS;
pthread_mutex_t lock;

void* sell_tickets(void* arg) {
    while (1) {
        pthread_mutex_lock(&lock); // 加锁
        
        if (ticket_count <= 0) {
            pthread_mutex_unlock(&lock); // 解锁
            break;
        }
        
        // 模拟售票处理时间
        struct timespec sleep_time = {0, 1000000}; // 1毫秒
        nanosleep(&sleep_time, NULL);
        
        printf("线程%ld卖出第%d张票\n", (long)arg, ticket_count);
        ticket_count--;
        
        pthread_mutex_unlock(&lock); // 解锁
    }
    return NULL;
}

int main() {
    pthread_t threads[NUM_THREADS];
    pthread_mutex_init(&lock, NULL);
    
    // 创建售票线程
    for (long i = 0; i < NUM_THREADS; i++) {
        pthread_create(&threads[i], NULL, sell_tickets, (void*)i);
    }
    
    // 等待所有线程完成
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }
    
    pthread_mutex_destroy(&lock);
    printf("所有票已售罄\n");
    return 0;
}

4. 线程安全

线程安全是指在多线程环境中,多个线程并发执行时,不会产生数据竞争、死锁等问题,保证程序的正确性

4.1 避免数据竞争

数据竞争发生在多个线程同时访问共享数据时,其中至少有一个线程进行写操作。如果没有同步机制,可能导致数据的不一致。常见的解决方法是使用 互斥锁原子操作