系统编程 · 代码笔记1 · 多线程

前言

1、编程环境

  编码所用IDE:VScode 1.87.0
  编译工具:gcc version 11.4.0

在这里插入图片描述

  运行环境:
    1、Windows Subsystem for Linux (WSL) 2
    2、Ubuntu 22.04.4 LTS

在这里插入图片描述

2、编译命令

  如无特别说明,通用编译命令为:

 gcc -o main -std=c99 -Wall -pthread filename.c

3、标题名前缀解释

  XXX1或XXX2中,XXX代表一个主题,凡是具有相同的XXX,都是同一主题。
  1代表服务端内容(代码),2代表客户端内容(代码)。


0010第一个多线程程序

  相关代码:

#include <stdio.h>   // 引入标准输入输出库
#include <pthread.h> // 引入POSIX线程库
#include <unistd.h>  // 引入unistd库,提供sleep函数的原型

/**
 * @brief 子线程的入口函数
 *        这个函数会在子线程启动时被调用执行。
 * @param arg 线程启动参数,这里未使用
 * @return void* 返回类型为void*,这里返回NULL
 */
void *threadFun(void *arg)
{
    printf("这是子线程。\n"); // 打印子线程信息
    return NULL;              // 子线程结束,返回NULL
}

int main(int argc, char const *argv[])
{
    pthread_t threadID; // 表示线程ID的变量

    // 创建一个新线程,线程ID存储在threadID中,新线程执行threadFun函数,不传递参数
    pthread_create(&threadID, NULL, threadFun, NULL);

    printf("这是主线程\n"); // 打印主线程信息
    sleep(1);               // 主线程休眠1秒,确保子线程有机会执行

    return 0; // 主线程正常退出,返回0
}

  运行结果:

在这里插入图片描述

0020多线程与显示线程ID

  相关代码:

#include <stdio.h>   // 引入标准输入输出库
#include <unistd.h>  // 引入unistd库,提供sleep函数的原型
#include <pthread.h> // 引入POSIX线程库

/**
 * @brief 子线程的入口函数
 *        这个函数会在每个子线程启动时被调用执行。
 * @param arg 线程启动参数,这里未使用
 * @return void* 返回类型为void*,这里返回NULL
 */
void *threadFun(void *arg)
{
    printf("这是子线程,ID为:%ld\n", pthread_self()); // 打印子线程的ID
    return NULL;                                       // 子线程结束,返回NULL
}

int main(int argc, char const *argv[])
{
    pthread_t threadID[5]; // 用于存储5个线程ID的数组

    // 创建5个新线程,每个线程执行threadFun函数,不传递参数
    for (int i = 0; i < 5; i++)
    {
        pthread_create(&threadID[i], NULL, threadFun, NULL);
    }

    printf("这是主线程。\n"); // 打印主线程信息
    sleep(1);                 // 主线程休眠1秒,确保子线程有机会执行

    return 0; // 主线程正常退出,返回0
}

  运行结果:
在这里插入图片描述

0030传参给线程测试

  相关代码:

#include <stdio.h>   // 引入标准输入输出库
#include <unistd.h>  // 引入unistd库,提供sleep函数的原型
#include <pthread.h> // 引入POSIX线程库

/**
 * @brief 子线程的入口函数
 *        这个函数会在每个子线程启动时被调用执行。
 * @param arg 线程启动参数,可以是任何类型的指针
 * @return void* 返回类型为void*,这里返回NULL
 */
void *threadFun(void *arg)
{
    if (arg == NULL)
    {
        printf("线程 %ld 未接收到参数。\n", pthread_self()); // 如果参数为NULL,打印未接收到参数的信息
    }
    else
    {
        printf("线程 %ld 接收到的参数为:%s\n", pthread_self(), (char *)arg); // 如果参数不为NULL,打印接收到的参数
    }

    return NULL; // 子线程结束,返回NULL
}

int main(int argc, const char *argv[])
{
    pthread_t threadID1, threadID2; // 用于存储两个线程ID的变量
    int res;                        // 用于存储线程创建函数的返回值

    // 创建第一个线程,不传递参数
    res = pthread_create(&threadID1, NULL, threadFun, NULL);
    if (res != 0)
    {
        perror("创建线程1失败!"); // 如果线程创建失败,打印错误信息
        return -1;                 // 创建失败,返回-1
    }

    // 创建第二个线程,传递命令行第一个参数作为线程参数
    const char *string = argv[0]; // 获取命令行第一个参数
    res = pthread_create(&threadID2, NULL, threadFun, (void *)string);
    if (res != 0)
    {
        perror("创建线程2失败!"); // 如果线程创建失败,打印错误信息
        return -1;                 // 创建失败,返回-1
    }

    sleep(1); // 主线程休眠1秒,确保子线程有机会执行

    return 0; // 主线程正常退出,返回0
}

  运行结果:
在这里插入图片描述

0041终止线程1_pthread_exit

  相关代码:

#include <stdio.h>   // 引入标准输入输出库
#include <unistd.h>  // 引入Unix标准函数库,提供对POSIX操作系统API的访问
#include <pthread.h> // 引入线程库,提供线程操作的相关函数

/**
 * threadFun - 线程执行函数
 * @arg: 线程函数的参数,这里未使用
 *
 * 描述:该函数使用pthread_exit退出,发送一个返回值。
 * 注意:pthread_exit会结束当前线程,并设置线程的退出状态。
 *       该函数不能返回局部变量的指针,因为当函数退出时,
 *       局部变量所占用的内存将被释放。
 */
void *threadFun(void *arg)
{
    /**
     * pthread_exit() 函数不能返回一个指向局部数据的指针
     * pthread_exit() 函数只会终止当前线程,不会影响其它线程的执行
     * pthread_exit() 函数会自动调用线程清理程序, return 不具备这个能力
     **/
    pthread_exit("subString_success"); // 设置线程的退出状态为"subString_success"
    // 以下代码不会被执行,因为pthread_exit会结束当前线程
    printf("子线程不会执行到此处");
}

int main(int argc, char const *argv[])
{
    pthread_t threadID; // 声明一个线程ID变量
    // 尝试创建一个新线程
    if (pthread_create(&threadID, NULL, threadFun, NULL))
    {
        perror("子线程创建失败!"); // 如果创建失败,打印错误信息
        return -1;                  // 创建失败,返回-1
    }

    void *subThreadRet = NULL;                       // 声明一个指针用于接收线程的返回值
    int res = pthread_join(threadID, &subThreadRet); // 等待线程结束,并获取返回值
    if (res != 0)
    {
        perror("子线程等待失败!"); // 如果等待失败,打印错误信息
        return -1;                  // 等待失败,返回-1
    }

    printf("接收到的返回值为:%s\n", (char *)subThreadRet); // 打印线程返回的信息

    return 0; // 程序执行成功,返回0
}

  运行结果:

在这里插入图片描述

0042终止线程2_pthread_cancel

  相关代码:

#include <stdio.h>   // 引入标准输入输出库
#include <pthread.h> // 引入POSIX线程库
#include <unistd.h>  // 引入unistd库,提供sleep函数的原型

/**
 * @brief 子线程的入口函数
 *        这个函数会在子线程启动时被调用执行。
 * @param arg 线程启动参数,这里未使用
 * @return void* 返回类型为void*,这里返回NULL
 */
void *threadFun(void *arg)
{
    printf("线程 %ld 已创建,且准备休眠\n", pthread_self()); // 打印子线程ID和准备休眠的信息
    sleep(10);                                               // 子线程休眠10秒
    return NULL;                                             // 子线程结束,返回NULL
}

int main(int argc, char const *argv[])
{
    pthread_t threadID; // 用于存储线程ID的变量
    int res;            // 用于存储线程操作函数的返回值

    // 创建一个新线程,线程ID存储在threadID中,新线程执行threadFun函数,不传递参数
    res = pthread_create(&threadID, NULL, threadFun, NULL);
    if (res != 0)
    {
        perror("线程创建失败!"); // 如果线程创建失败,打印错误信息
        return -1;                // 创建失败,返回-1
    }
    sleep(1); // 主线程休眠1秒,确保子线程有机会执行

    // 向目标线程发送 Cancel 信号,目标线程是否响应,由目标线程决定
    res = pthread_cancel(threadID);
    if (res != 0)
    {
        perror("线程取消失败!"); // 如果取消线程失败,打印错误信息
        return -1;                // 取消失败,返回-1
    }

    void *subThreadRet = NULL; // 用于存储子线程返回值的指针
    // 等待子线程结束,并获取子线程的返回值
    res = pthread_join(threadID, &subThreadRet);
    if (res != 0)
    {
        perror("等待线程失败!"); // 如果等待线程失败,打印错误信息
        return -1;                // 等待失败,返回-1
    }

    // 检查子线程是否被取消
    if (subThreadRet == PTHREAD_CANCELED)
    {
        printf("线程被强制停止!\n"); // 子线程被取消,打印相关信息
    }
    else
    {
        printf("无法强制停止线程。\n"); // 子线程未被取消,打印相关信息
    }

    return 0; // 主线程正常退出,返回0
}

  运行结果:

在这里插入图片描述

0050接收返回值_pthread_join

  相关代码:

#include <stdio.h>   // 引入标准输入输出库
#include <stdlib.h>  // 引入标准库,提供malloc和free函数
#include <pthread.h> // 引入线程库
#include <errno.h>   // 引入错误号库

// 定义线程结果的结构体
typedef struct
{
    int value;        // 整数值
    char message[50]; // 信息字符串
} ThreadResult;

/**
 * @brief 线程函数,用于处理传入的整型参数,并返回一个包含处理结果的ThreadResult结构体指针。
 *
 * @param arg 指向整型参数的指针。
 * @return void* 返回一个指向ThreadResult结构体的指针,包含处理结果。
 */
void *threadFun(void *arg)
{
    int num = *(int *)arg;                       // 获取传入的参数
    printf("子线程所接收到的参数为:%d\n", num); // 打印参数值

    ThreadResult *result = (ThreadResult *)malloc(sizeof(ThreadResult)); // 分配内存
    result->value = num * 2;                                             // 计算结果
    sprintf(result->message, "子线程的处理结果为:%d", result->value);   // 格式化信息字符串

    pthread_exit(result); // 退出线程,并返回结果
}

int main(int argc, char const *argv[])
{
    pthread_t threadID; // 线程ID
    int threadArg = 10; // 传递给线程的参数

    // 创建线程
    if (pthread_create(&threadID, NULL, threadFun, (void *)&threadArg) != 0)
    {
        perror("创建线程失败!"); // 如果创建失败,打印错误信息
        return -1;                // 返回错误码
    }

    void *subThreadRet = NULL; // 子线程返回的结果
    // 等待线程结束,并获取返回值
    if (pthread_join(threadID, &subThreadRet) != 0)
    {
        perror("等待线程失败!"); // 如果等待失败,打印错误信息
        return -1;                // 返回错误码
    }
    ThreadResult *result = (ThreadResult *)subThreadRet;                                                  // 获取结果
    printf("主线程接收到子线程返回的数据为:\n整型数据:%d\t信息:%s\n", result->value, result->message); // 打印结果

    free(result); // 释放内存

    // 再次尝试等待同一个线程,预期会失败
    int res = pthread_join(threadID, &subThreadRet);
    if (res == ESRCH)
    {
        perror("等待线程失败!"); // 如果失败,打印错误信息
    }

    return 0; // 程序结束
}

  运行结果:

在这里插入图片描述

0060线程响应cancel信号及响应的方式

  相关代码:

#include <unistd.h> // 包含unistd.h头文件,提供对POSIX操作系统API的访问
#include <stdio.h>  // 包含stdio.h头文件,提供标准输入输出库函数
#include <pthread.h> // 包含pthread.h头文件,提供POSIX线程操作函数
#include <stdlib.h>  // 包含stdlib.h头文件,提供常用工具函数如exit()

/**
 * threadFun - 线程的执行函数
 * @arg: 线程参数,未使用
 *
 * 返回:NULL
 *
 * 功能:打印线程开始执行的信息,设置线程可以被取消,并进入一个无限循环。
 */
void *threadFun(void *arg)
{
    printf("线程 %ld 开始执行。\n", pthread_self()); // 打印线程ID和开始执行的信息
    // 用于设置线程是否响应取消请求
    if (pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0)
    {
        perror("设置可取消线程运行状态失败!"); // 如果设置失败,打印错误信息
        return NULL; // 返回NULL,表示线程执行失败
    }

    //  用于设置线程收到取消请求后的行为
    if (pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL) != 0)
    {
        perror("设置线程接收到cancel信号则直接结束运行状态失败!"); // 如果设置失败,打印错误信息
        return NULL; // 返回NULL,表示线程执行失败
    }

    while (1) // 无限循环,等待线程被取消
        ;
    return NULL; // 正常情况下不会执行到这里
}

/**
 * main - 主函数
 * @argc: 命令行参数数量
 * @argv: 命令行参数数组
 *
 * 返回:返回码,0表示成功,-1表示失败
 *
 * 功能:创建一个线程,等待一秒后尝试取消该线程,并等待线程结束,最后输出线程的结束状态。
 */
int main(int argc, char const *argv[])
{
    pthread_t threadID; // 定义线程ID变量
    if (pthread_create(&threadID, NULL, threadFun, NULL) != 0)
    {
        perror("创建子线程失败!"); // 如果创建失败,打印错误信息
        return -1; // 返回-1,表示程序执行失败
    }
    sleep(1); // 休眠1秒,等待线程开始执行

    if (pthread_cancel(threadID) != 0)
    {
        perror("取消线程失败!"); // 如果取消失败,打印错误信息
        return -1; // 返回-1,表示程序执行失败
    }

    void *subThreadRet = NULL; // 定义子线程返回值变量
    if (pthread_join(threadID, &subThreadRet) != 0)
    {
        perror("等待线程运行结束失败!"); // 如果等待失败,打印错误信息
        return -1; // 返回-1,表示程序执行失败
    }

    if (subThreadRet == PTHREAD_CANCELED)
    {
        printf("线程被强制终止!\n"); // 如果线程被取消,打印线程被强制终止的信息
    }
    else
    {
        printf("强制终止线程失败!\n"); // 如果线程未被取消,打印强制终止线程失败的信息
    }

    return 0; // 返回0,表示程序执行成功
}

  运行结果:

在这里插入图片描述

0070线程执行多个任务函数

  相关代码:

#include <stdio.h>   // 引入标准输入输出库
#include <unistd.h>  // 引入Unix标准函数库,主要处理Unix标准系统调用
#include <stdlib.h>  // 引入标准库,主要包含了一些常用工具函数
#include <pthread.h> // 引入多线程处理库

/**
 * threadFun2 - 输出当前线程ID的函数
 *
 * 返回:无
 */
void threadFun2(void)
{
    printf("threadFun2 => 本线程的ID为:%ld\n", pthread_self()); // 输出当前线程的ID
}

/**
 * threadFun1 - 输出当前线程ID并调用threadFun2的函数
 * @arg: 线程参数,这里未使用
 *
 * 返回:无实际意义,仅为了满足函数指针类型要求
 */
void *threadFun1(void *arg)
{
    printf("threadFun1 => 本线程的ID为:%ld\n", pthread_self()); // 输出当前线程的ID
    threadFun2();                                                // 调用threadFun2函数
    return NULL;                                                 // 线程函数返回NULL
}

/**
 * main - 主函数,程序的入口
 * @argc: 命令行参数个数
 * @argv: 命令行参数数组
 *
 * 返回:整数,程序退出状态码
 */
int main(int argc, char const *argv[])
{
    pthread_t threadID;                                // 声明一个线程ID变量
    pthread_create(&threadID, NULL, threadFun1, NULL); // 创建一个新线程,执行threadFun1函数

    pthread_join(threadID, NULL); // 等待指定的线程结束

    return 0; // 程序正常退出
}

  运行结果:

在这里插入图片描述

0080LTS线程局部存储技术

  相关代码:

#include <stdio.h>   // 包含标准输入输出库函数
#include <stdlib.h>  // 包含常用工具函数如malloc()和free()
#include <unistd.h>  // 包含unistd.h头文件,提供对POSIX操作系统API的访问
#include <pthread.h> // 包含pthread.h头文件,提供POSIX线程操作函数

pthread_key_t key; // 声明一个线程特定数据(TSD)键

/**
 * cleanup - 线程清理函数
 * @value: 需要清理的数据
 *
 * 功能:打印线程ID和需要清理的key值,并释放内存。
 */
void cleanup(void *value)
{
    printf("线程ID:%ld,需要清理的key值:%d\n", pthread_self(), *(int *)value);
    free(value); // 释放分配的内存
}

/**
 * print_specific - 打印线程特定数据
 *
 * 功能:获取并打印当前线程的特定数据。
 */
void print_specific(void)
{
    int *thread_data = (int *)pthread_getspecific(key); // 获取当前线程的特定数据
    printf("线程ID:%ld,线程的key值:%d\n", pthread_self(), *thread_data);
}

/**
 * threadFun - 线程执行函数
 * @arg: 线程参数,指向需要设置的特定数据的指针
 *
 * 返回:NULL
 *
 * 功能:为当前线程分配特定数据,并设置到TSD中,然后打印出来。
 */
void *threadFun(void *arg)
{
    int *thread_data = (int *)malloc(sizeof(int));  // 分配内存用于存储特定数据
    *thread_data = *(int *)arg;                     // 将线程参数复制到分配的内存中
    if (pthread_setspecific(key, thread_data) != 0) // 将特定数据设置到TSD中
    {
        perror("存储线程的值失败!"); // 如果设置失败,打印错误信息
        return NULL;                  // 返回NULL,表示线程执行失败
    }

    print_specific(); // 打印当前线程的特定数据

    return NULL; // 线程执行完毕,返回NULL
}

/**
 * main - 主函数
 *
 * 返回:返回码,0表示成功,-1表示失败
 *
 * 功能:创建一个TSD键,创建并等待多个线程,每个线程设置并打印自己的特定数据,最后销毁TSD键。
 */
int main(int argc, char const *argv[])
{
    if (pthread_key_create(&key, cleanup) != 0) // 创建TSD键并设置清理函数
    {
        perror("初始化key失败!"); // 如果创建失败,打印错误信息
        return -1;                 // 返回-1,表示程序执行失败
    }

    pthread_t threadID[5];   // 声明线程ID数组
    int thread_arg[5] = {0}; // 初始化线程参数数组
    for (int i = 0; i < 5; i++)
    {
        thread_arg[i] = i; // 设置每个线程的参数
        if (pthread_create(&threadID[i], NULL, threadFun, (void *)&thread_arg[i]) != 0)
        {
            perror("线程创建失败!"); // 如果创建失败,打印错误信息
            return -1;                // 返回-1,表示程序执行失败
        }
    }

    for (int i = 0; i < 5; i++)
    {
        if (pthread_join(threadID[i], NULL) != 0) // 等待每个线程结束
        {
            perror("等待线程失败!"); // 如果等待失败,打印错误信息
            return -1;                // 返回-1,表示程序执行失败
        }
    }

    if (pthread_key_delete(key) != 0) // 销毁TSD键
    {
        perror("主线程销毁key失败!"); // 如果销毁失败,打印错误信息
        return -1;                     // 返回-1,表示程序执行失败
    }

    return 0; // 返回0,表示程序执行成功
}

  运行结果:

在这里插入图片描述

0090线程不同步导致的数据错乱问题

  相关代码:

#include <stdio.h>   // 引入标准输入输出库
#include <stdlib.h>  // 引入标准库,主要包含了一些常用工具函数
#include <pthread.h> // 引入多线程处理库
#include <unistd.h>  // 引入Unix标准函数库,主要处理Unix标准系统调用

// 全局变量,模拟总的票数
int ticket_sum = 10;

/**
 * sell_ticket - 模拟售票员卖票的函数
 * @arg: 线程参数,这里未使用
 *
 * 返回:无实际意义,仅为了满足函数指针类型要求
 */
void *sell_ticket(void *arg)
{
    int i;
    // 4个售票员负责将10张票全部卖出
    for (i = 0; i < 10; i++)
    {
        // 直至所有票全部卖出,4个售票员才算完成任务
        if (ticket_sum > 0)
        {
            sleep(1); // 模拟售票过程中时间的消耗
            // 每个线程代表一个售票员
            printf("%ld 卖第 %d 张票\n", pthread_self(), 10 - ticket_sum + 1);
            ticket_sum--; // 票数减少
        }
    }
    return 0;
}

/**
 * main - 主函数,程序的入口
 *
 * 返回:整数,程序退出状态码
 */
int main(int argc, char const *argv[])
{
    // 创建4个线程,代表4个售票员
    pthread_t tids[4];
    for (int i = 0; i < 4; i++)
    {
        if (pthread_create(&tids[i], NULL, &sell_ticket, NULL) != 0)
        {
            printf("线程创建失败!\n"); // 如果线程创建失败,输出错误信息
            return 0;
        }
    }
    sleep(10); // 阻塞主线程,等待所有子线程执行结束
    for (int i = 0; i < 4; i++)
    {
        void *ans = NULL;
        int flag = pthread_join(tids[i], &ans); // 等待线程结束
        if (flag != 0)
        {
            printf("tid=%ld 等待失败!\n", tids[i]); // 如果等待线程失败,输出错误信息
            return 0;
        }
    }
    return 0; // 程序正常退出
}

  运行结果:

  其实从这里已经看出来,总共10张票,但是卖出了13张,这就是线程不同步造成的后果

在这里插入图片描述

0101使用宏定义初始化互斥锁

  相关代码:

#include <stdio.h>   // 包含标准输入输出库函数
#include <stdlib.h>  // 包含常用工具函数如exit()
#include <pthread.h> // 包含pthread.h头文件,提供POSIX线程操作函数
#include <unistd.h>  // 包含unistd.h头文件,提供对POSIX操作系统API的访问

int ticket_sum = 10; // 全局变量,表示总票数

// 声明一个互斥锁,用于同步线程对票数的访问
pthread_mutex_t myMutex = PTHREAD_MUTEX_INITIALIZER;

/**
 * threadFun - 线程执行函数
 * @arg: 线程参数,未使用
 *
 * 返回:NULL
 *
 * 功能:模拟售票过程,每个线程尝试卖出10张票。
 */
void *threadFun(void *arg)
{
    printf("线程ID:%ld 已启动\n", pthread_self()); // 打印线程ID和启动信息

    for (int i = 0; i < 10; i++)
    {
        // 尝试加锁,防止多个线程同时操作票数
        if (pthread_mutex_lock(&myMutex) == 0)
        {
            if (ticket_sum > 0) // 如果还有票,则卖出一张
            {
                sleep(1); // 模拟售票过程需要的时间
                printf("线程ID %ld 卖出第 %d 张票\n", pthread_self(), 10 - ticket_sum + 1);
                --ticket_sum; // 票数减一
            }
            pthread_mutex_unlock(&myMutex); // 解锁
        }
    }

    return NULL; // 线程执行完毕,返回NULL
}

/**
 * main - 主函数
 *
 * 返回:返回码,0表示成功,-1表示失败
 *
 * 功能:创建四个售票线程,等待所有线程结束。
 */
int main(int argc, char const *argv[])
{
    pthread_t threadID[4]; // 声明线程ID数组

    for (int i = 0; i < 4; i++)
    {
        if (pthread_create(&threadID[i], NULL, threadFun, NULL) != 0)
        {
            perror("线程创建失败!"); // 如果创建失败,打印错误信息
            return -1;                // 返回-1,表示程序执行失败
        }
    }
    sleep(1); // 主线程休眠1秒,确保所有子线程都有机会运行

    for (int i = 0; i < 4; i++)
    {
        if (pthread_join(threadID[i], NULL) != 0)
        {
            perror("线程等待失败!"); // 如果等待失败,打印错误信息
            return -1;                // 返回-1,表示程序执行失败
        }
    }

    return 0; // 返回0,表示程序执行成功
}

  运行结果:

在这里插入图片描述

0102使用函数初始化互斥锁

  相关代码:

#include <stdio.h>   // 引入标准输入输出库
#include <pthread.h> // 引入多线程处理库
#include <time.h>    // 引入时间处理库

// 声明一个互斥锁
pthread_mutex_t myMutex;

// 全局共享资源
int shareResource = 1;

/**
 * increment - 一个线程函数,用于增加共享资源的值
 * @arg: 线程参数,这里未使用
 *
 * 返回:NULL
 */
void *increment(void *arg)
{
    struct timespec req, rem; // 用于nanosleep的结构体

    for (int i = 0; i < 3; i++)
    {
        req.tv_sec = 1;   // 设置睡眠时间为1秒
        req.tv_nsec = 0L; // 纳秒部分设置为0

        pthread_mutex_lock(&myMutex);                  // 加锁,保护共享资源
        nanosleep(&req, &rem);                         // 线程睡眠1秒
        printf("当前资源值为:%d\n", ++shareResource); // 输出资源值并自增
        pthread_mutex_unlock(&myMutex);                // 解锁
    }
    return NULL;
}

/**
 * decrement - 一个线程函数,用于减少共享资源的值
 * @arg: 线程参数,这里未使用
 *
 * 返回:NULL
 */
void *decrement(void *arg)
{
    struct timespec req, rem; // 用于nanosleep的结构体

    for (int i = 0; i < 3; i++)
    {
        req.tv_sec = 1;   // 设置睡眠时间为1秒
        req.tv_nsec = 0L; // 纳秒部分设置为0

        pthread_mutex_lock(&myMutex);                  // 加锁,保护共享资源
        nanosleep(&req, &rem);                         // 线程睡眠1秒
        printf("当前资源值为:%d\n", --shareResource); // 输出资源值并自减
        pthread_mutex_unlock(&myMutex);                // 解锁
    }
    return NULL;
}

/**
 * main - 主函数,程序的入口
 * @argc: 命令行参数个数
 * @argv: 命令行参数数组
 *
 * 返回:整数,程序退出状态码
 */
int main(int argc, char const *argv[])
{
    pthread_mutex_init(&myMutex, NULL); // 初始化互斥锁

    pthread_t incThread, decThread; // 声明两个线程ID变量

    pthread_create(&incThread, NULL, increment, NULL); // 创建增加资源的线程
    pthread_create(&decThread, NULL, decrement, NULL); // 创建减少资源的线程

    pthread_join(incThread, NULL); // 等待增加资源的线程结束
    pthread_join(decThread, NULL); // 等待减少资源的线程结束

    printf("共享资源最后的值为:%d\n", shareResource); // 输出共享资源的最终值

    pthread_mutex_destroy(&myMutex); // 销毁互斥锁

    return 0; // 程序正常退出
}

  运行结果:

  从这里看到资源之是顺序递增或者递减的,中间没有跳跃式变动,这证明了互斥锁保证了线程同步。

在这里插入图片描述

0111信号量_二进制信号量

  相关代码:

#include <stdio.h>     // 包含标准输入输出库函数
#include <pthread.h>   // 包含pthread.h头文件,提供POSIX线程操作函数
#include <semaphore.h> // 包含semaphore.h头文件,提供信号量操作函数
#include <unistd.h>    // 包含unistd.h头文件,提供对POSIX操作系统API的访问

sem_t semaphore; // 声明一个信号量

int shared_resource = 0; // 全局变量,表示共享资源

/**
 * threadFun - 线程执行函数
 * @arg: 线程参数,传递线程编号
 *
 * 返回:NULL
 *
 * 功能:每个线程尝试三次增加共享资源的值,并打印出来。
 */
void *threadFun(void *arg)
{
    for (int i = 0; i < 3; i++)
    {
        sem_wait(&semaphore); // P操作,信号量减一,如果信号量为0,则线程阻塞
        ++shared_resource;    // 增加共享资源的值
        printf("线程 %d:当前共享资源的值为:%d\n", *(int *)arg, shared_resource);
        sem_post(&semaphore); // V操作,信号量加一,唤醒等待的线程
    }
    return NULL; // 线程执行完毕,返回NULL
}

/**
 * main - 主函数
 *
 * 返回:返回码,0表示成功
 *
 * 功能:初始化信号量,创建两个线程,等待线程结束,销毁信号量。
 */
int main(int argc, char const *argv[])
{
    pthread_t tid1, tid2; // 声明线程ID变量

    sem_init(&semaphore, 0, 1); // 初始化信号量,初始值为1

    int number1 = 1, number2 = 2; // 线程编号

    pthread_create(&tid1, NULL, threadFun, (void *)&number1); // 创建第一个线程
    pthread_create(&tid2, NULL, threadFun, (void *)&number2); // 创建第二个线程

    pthread_join(tid1, NULL); // 等待第一个线程结束
    pthread_join(tid2, NULL); // 等待第二个线程结束

    sem_destroy(&semaphore); // 销毁信号量

    return 0; // 返回0,表示程序执行成功
}

  运行结果:

  从这里看出,共享资源是顺序增长的,这代表了二进制信号量保证了线程同步
在这里插入图片描述

0112信号量_计数信号量

  相关代码:

#include <unistd.h>    // 引入Unix标准函数库,主要处理Unix标准系统调用
#include <stdio.h>     // 引入标准输入输出库
#include <pthread.h>   // 引入多线程处理库
#include <stdlib.h>    // 引入标准库,主要包含了一些常用工具函数
#include <semaphore.h> // 引入信号量库

// 设置办理业务的人数
int num = 5;
// 创建信号量
sem_t sem;

/**
 * get_service - 模拟客户办理业务的函数
 * @arg: 线程参数,这里是客户的编号
 *
 * 返回:无实际意义,仅为了满足函数指针类型要求
 */
void *get_service(void *arg)
{
    int id = *((int *)arg);
    // 信号量成功“减1”后才能继续执行
    if (sem_wait(&sem) == 0)
    {
        printf("---customer%d 正在办理业务\n", id);
        sleep(2); // 模拟办理业务所需时间
        printf("---customer%d 已办完业务\n\n", id);
        // 信号量“加1”
        sem_post(&sem);
    }
    return 0;
}

/**
 * main - 主函数,程序的入口
 *
 * 返回:整数,程序退出状态码
 */
int main()
{
    int flag, i, j;
    // 创建5个线程代表5个人
    pthread_t customer[5];
    // 初始化信号量,允许同时有两个线程执行
    sem_init(&sem, 0, 2);
    for (i = 0; i < num; i++)
    {
        flag = pthread_create(&customer[i], NULL, get_service, &i);
        if (flag != 0)
        {
            printf("线程创建失败!\n"); // 如果线程创建失败,输出错误信息
            return 0;
        }
        else
        {
            printf("\ncustomer%d 来办理业务\n", i); // 输出客户到达信息
        }
        sleep(1); // 模拟客户到达的时间间隔
    }

    for (j = 0; j < num; j++)
    {
        flag = pthread_join(customer[j], NULL); // 等待线程结束
        if (flag != 0)
        {
            printf("tid = %ld 等待失败!", customer[j]); // 如果等待线程失败,输出错误信息
            return 0;
        }
    }
    sem_destroy(&sem); // 销毁信号量
    return 0;          // 程序正常退出
}

  运行结果:

  从程序的运行结果可以看出,来办理业务的“客户”是一个个来的,每个过来的客户都会检查是否还有剩余的“信号量”,如果没有,则不会轮到下一个人办理业务。这样就保证了一个个“客户”按顺序办理业务。
在这里插入图片描述

0120条件变量与互斥锁

  相关代码:

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

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int item_to_consume = 0;

/**
 * @brief 生产者线程函数,用于生产物品并通知消费者
 *
 * 这个函数是一个线程的执行主体,它不断地生产物品,并通知消费者线程。
 * 函数使用一个无限循环来实现连续的生产过程。
 *
 * @param arg 线程参数,本函数未使用
 * @return void* 线程返回值,本函数返回NULL
 */
void *producer(void *arg)
{
    while (1)
    {
        pthread_mutex_lock(&mutex);
        printf("生产者生产了一个物品,现在的物品总数为:%d\n", ++item_to_consume);

        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);

        sleep(1);
    }
    return NULL;
}

/**
 * @brief 消费者线程函数,用于消费物品
 *
 * 这个函数是一个线程的执行主体,它不断地尝试消费物品。
 * 如果没有物品可消费,线程将等待生产者的通知。
 *
 * @param arg 线程参数,本函数未使用
 * @return void* 线程返回值,本函数返回NULL
 */
void *consumer(void *arg)
{
    while (1)
    {
        pthread_mutex_lock(&mutex);

        // 如果没有物品,则等待生产者的通知
        while (item_to_consume == 0)
        {
            pthread_cond_wait(&cond, &mutex);
        }

        printf("消费者消费了一个物品,现在物品总数为:%d\n", --item_to_consume);

        pthread_mutex_unlock(&mutex);

        sleep(1);
    }

    return NULL;
}

int main(int argc, char const *argv[])
{
    pthread_t producer_threadID, consumer_threadID;

    pthread_create(&producer_threadID, NULL, producer, NULL);
    pthread_create(&consumer_threadID, NULL, consumer, NULL);

    pthread_join(producer_threadID, NULL);
    pthread_join(consumer_threadID, NULL);

    return 0;
}

  运行结果:

  注意! 由于生成和消费都是进入死循环,此程序需要按下CTRL+C来强制结束
在这里插入图片描述

0130读写锁

注意! 此程序需要使用-std=gnu99参数来进行编译,而非-std=c99

gcc -o main -std=gnu99 -Wall -pthread ./0130读写锁.c

  相关代码:

#include <stdio.h>   // 引入标准输入输出头文件
#include <pthread.h> // 引入多线程操作相关的头文件
#include <unistd.h>  // 引入系统调用相关的头文件

#define COUNT 3 // 定义常量,表示读线程的数量

int x = 0;               // 全局变量,用于线程间共享数据
pthread_rwlock_t rwlock; // 定义一个读写锁,用于控制对共享资源的访问

/**
 * read_thread - 读锁线程的处理函数
 * @arg: 线程参数,本例中未使用
 *
 * 该函数使用读锁访问共享资源,并打印读取到的数据。
 * 循环执行,每秒执行一次。
 */
void *read_thread(void *arg)
{
    printf("读锁线程 %ld 准备完毕。\n", pthread_self()); // 打印线程ID,表明线程准备就绪

    while (1) // 无限循环
    {
        sleep(1); // 休眠1秒

        pthread_rwlock_rdlock(&rwlock);                                 // 加读锁
        printf("读锁线程 %ld ,读取到的数据:%d\n", pthread_self(), x); // 打印线程ID和读取到的数据
        pthread_rwlock_unlock(&rwlock);                                 // 解锁
    }
    return NULL; // 线程函数返回NULL
}

/**
 * write_thread - 写锁线程的处理函数
 * @arg: 线程参数,本例中未使用
 *
 * 该函数使用写锁修改共享资源,并打印修改后的数据。
 * 循环执行,每秒执行一次。
 */
void *write_thread(void *arg)
{
    printf("写锁线程 %ld 准备完毕。\n", pthread_self()); // 打印线程ID,表明线程准备就绪

    while (1) // 无限循环
    {
        sleep(1); // 休眠1秒

        pthread_rwlock_wrlock(&rwlock);                                 // 加写锁
        printf("写锁线程 %ld ,当前数据为:%d\n", pthread_self(), ++x); // 打印线程ID和修改后的数据
        pthread_rwlock_unlock(&rwlock);                                 // 解锁
    }

    return NULL; // 线程函数返回NULL
}

int main(int argc, char const *argv[]) // 程序入口函数
{
    pthread_rwlock_init(&rwlock, NULL); // 初始化读写锁

    pthread_t readThreadID[COUNT];  // 定义读线程ID数组
    for (int i = 0; i < COUNT; i++) // 循环创建读线程
    {
        pthread_create(&readThreadID[i], NULL, read_thread, NULL); // 创建读线程
    }

    pthread_t write_threadID;                                  // 定义写线程ID
    pthread_create(&write_threadID, NULL, write_thread, NULL); // 创建写线程

    for (int i = 0; i < COUNT; i++) // 等待所有读线程结束
    {
        pthread_join(readThreadID[i], NULL); // 等待读线程结束
    }

    pthread_join(write_threadID, NULL); // 等待写线程结束

    pthread_rwlock_destroy(&rwlock); // 销毁读写锁

    return 0; // 主函数返回0,表明程序正常结束
}

  运行结果:

在这里插入图片描述

0141死锁案例1_单互斥锁

  相关代码:

#include <stdio.h>   // 引入标准输入输出头文件
#include <pthread.h> // 引入多线程操作头文件
#include <unistd.h>  // 引入系统调用头文件

// 初始化互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

/**
 * 线程函数,用于演示互斥锁的使用
 * @param arg 线程参数(本例中未使用)
 * @return void* 线程返回值(本例中返回NULL)
 */
void *threadFun(void *arg)
{
    // 尝试对互斥锁进行加锁,如果成功则打印信息
    if (pthread_mutex_lock(&mutex) == 0)
    {
        printf("线程 %ld 已成功加锁!\n", pthread_self());
    }

    // 本例中线程不需要返回值,故返回NULL
    return NULL;
}

/**
 * 主函数,程序入口
 * @param argc 命令行参数个数
 * @param argv 命令行参数数组
 * @return int 程序退出状态码
 */
int main(int argc, char const *argv[])
{
    pthread_t threadID[3]; // 定义线程ID数组
    for (int i = 0; i < 3; i++)
    {
        // 创建线程,并传递NULL作为线程参数
        pthread_create(&threadID[i], NULL, threadFun, NULL);
        // 打印线程创建完成信息
        printf("线程 %ld 创建完成!\n", threadID[i]);
    }

    for (int i = 0; i < 3; i++)
    {
        // 等待线程执行完成
        pthread_join(threadID[i], NULL);
        // 打印线程执行完成信息
        printf("线程 %ld 执行完成!\n", threadID[i]);
    }

    // 程序正常退出
    return 0;
}

  运行结果:

  忘记解互斥锁将会造成死锁

在这里插入图片描述

0142死锁案例2_双互斥锁

  相关代码:

#include <stdio.h>   // 包含标准输入输出库函数
#include <pthread.h> // 包含pthread.h头文件,提供POSIX线程操作函数
#include <unistd.h>  // 包含unistd.h头文件,提供对POSIX操作系统API的访问

// 声明并初始化两个互斥锁
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;

/**
 * threadFun1 - 线程1的执行函数
 * @arg: 线程参数,未使用
 *
 * 返回:NULL
 *
 * 功能:线程1首先获取mutex1锁,然后等待2秒,再尝试获取mutex2锁。
 */
void *threadFun1(void *arg)
{
    pthread_mutex_lock(&mutex1); // 尝试获取mutex1锁
    printf("线程1 申请到 mutex1 锁!\n");

    sleep(2); // 线程1休眠2秒

    pthread_mutex_lock(&mutex2); // 尝试获取mutex2锁
    printf("线程1 申请到 mutex2 锁!\n");

    pthread_mutex_unlock(&mutex1); // 释放mutex1锁
    printf("线程1释放了 mutex1 锁\n");

    return NULL; // 线程执行完毕,返回NULL
}

/**
 * threadFun2 - 线程2的执行函数
 * @arg: 线程参数,未使用
 *
 * 返回:NULL
 *
 * 功能:线程2首先获取mutex2锁,然后等待2秒,再尝试获取mutex1锁。
 */
void *threadFun2(void *arg)
{
    pthread_mutex_lock(&mutex2); // 尝试获取mutex2锁
    printf("线程2 申请到 mutex2 锁!\n");

    sleep(2); // 线程2休眠2秒

    pthread_mutex_lock(&mutex1); // 尝试获取mutex1锁
    printf("线程2 申请到 mutex1 锁!\n");

    pthread_mutex_unlock(&mutex2); // 释放mutex2锁
    printf("线程2释放了 mutex2 锁\n");

    return NULL; // 线程执行完毕,返回NULL
}

/**
 * main - 主函数
 *
 * 返回:返回码,0表示成功
 *
 * 功能:创建两个线程,等待线程结束。
 */
int main(int argc, char const *argv[])
{
    pthread_t threadID1, threadID2; // 声明线程ID变量

    pthread_create(&threadID1, NULL, threadFun1, NULL); // 创建线程1
    pthread_create(&threadID2, NULL, threadFun2, NULL); // 创建线程2

    pthread_join(threadID1, NULL); // 等待线程1结束
    pthread_join(threadID2, NULL); // 等待线程2结束

    return 0; // 返回0,表示程序执行成功
}

  运行结果:

  互相上锁将会造成死锁

在这里插入图片描述

0151线程分离1_pthread_detach函数

  相关代码:

#include <stdio.h>   // 引入标准输入输出库
#include <pthread.h> // 引入多线程处理库
#include <unistd.h>  // 引入Unix标准函数库,主要处理Unix标准系统调用

/**
 * threadFun - 子线程执行的函数
 * @arg: 线程参数,这里未使用
 *
 * 返回:NULL
 */
void *threadFun(void *arg)
{
    printf("子线程开始执行。\n");
    sleep(2); // 模拟子线程执行时间
    printf("子线程执行完毕。\n");
    pthread_exit(NULL); // 子线程退出
    // return NULL; // 这一行代码实际上不会执行,因为pthread_exit会终止线程
}

/**
 * main - 主函数,程序的入口
 * @argc: 命令行参数个数
 * @argv: 命令行参数数组
 *
 * 返回:整数,程序退出状态码
 */
int main(int argc, char const *argv[])
{
    pthread_t threadID;                               // 声明一个线程ID变量
    pthread_create(&threadID, NULL, threadFun, NULL); // 创建一个新线程
    pthread_detach(threadID);                         // 分离线程,线程资源将被立即释放

    printf("线程已经分离,主线程将继续执行\n");
    sleep(1); // 模拟主线程执行时间
    printf("主线程执行完毕!\n");

    return 0; // 程序正常退出
}

  运行结果:

在这里插入图片描述

0152线程分离2_pthread_attr_init函数

  相关代码:

#include <stdio.h>   // 引入标准输入输出头文件
#include <pthread.h> // 引入多线程操作相关的头文件
#include <unistd.h>  // 引入系统调用相关的头文件

/**
 * threadFun - 子线程的执行函数
 * @arg: 线程参数,本例中未使用
 *
 * 该函数用于演示子线程的执行过程,线程将睡眠2秒然后退出。
 */
void *threadFun(void *arg)
{
    printf("子线程开始执行。\n"); // 打印信息,表明子线程开始执行
    sleep(2);                     // 线程睡眠2秒
    printf("子线程执行完毕。\n"); // 打印信息,表明子线程执行完毕
    pthread_exit(NULL);           // 子线程退出,释放资源
    // return NULL;  // 该行代码实际上不会执行,因为pthread_exit已经退出线程
}

int main(int argc, char const *argv[])
{
    pthread_attr_t attr;                                         // 定义线程属性变量
    pthread_attr_init(&attr);                                    // 初始化线程属性
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 设置线程属性为分离状态

    pthread_t threadID;                                // 定义线程ID变量
    pthread_create(&threadID, &attr, threadFun, NULL); // 创建子线程

    pthread_attr_destroy(&attr); // 销毁线程属性对象,释放资源

    printf("线程已经分离,主线程将继续执行\n"); // 打印信息,表明线程已分离,主线程继续执行
    sleep(1);                                   // 主线程睡眠1秒
    printf("主线程执行完毕!\n");               // 打印信息,表明主线程执行完毕

    return 0; // 主函数返回0,表明程序正常结束
}

  运行结果:

在这里插入图片描述


额外内容

  此部分内容与多线程无关,仅作为拓展内容。

9011nanosleep延时函数1

  相关代码:

#include <stdio.h> // 包含标准输入输出库函数
#include <time.h>  // 包含time.h头文件,提供时间和日期操作函数
#include <errno.h> // 包含errno.h头文件,提供错误号定义

int main(int argc, char const *argv[])
{
    struct timespec req, rem; // 定义timespec结构体变量,用于nanosleep函数

    req.tv_sec = 1;                                 // 设置秒数为1秒
    req.tv_nsec = (long)(0.5 * 1000 * 1000 * 1000); // 设置纳秒数为0.5秒转换为纳秒

    /**
     * 如果 nanosleep 函数因为捕获到信号而提前返回,它会把剩余的延时时间填充至 rem 指向的结构体中,并返回-1。
     * 如果延时正常完成,则返回0。
     */
    while (nanosleep(&req, &rem) == -1)
    {
        if (errno == EINTR) // 如果错误号是EINTR,表示函数被信号中断
        {
            printf("nanosleep函数被正常中断\n"); // 打印被中断的信息
            req = rem;                           // 更新req为剩余的时间
        }
        else
        {
            perror("执行nanosleep函数出现错误!"); // 如果是其他错误,打印错误信息
            return -1;                             // 返回-1,表示程序执行失败
        }
    }

    printf("延时结束,程序正常结束运行。\n"); // 延时完成,打印结束信息

    return 0; // 返回0,表示程序执行成功
}

  运行结果:

在这里插入图片描述

9012nanosleep延时函数2

  相关代码:

#include <stdio.h> // 引入标准输入输出库
#include <time.h>  // 引入时间处理库

/**
 * main - 主函数,程序的入口
 * @argc: 命令行参数个数
 * @argv: 命令行参数数组
 *
 * 返回:整数,程序退出状态码
 */
int main(int argc, char const *argv[])
{
    struct timespec req, rem; // 定义timespec结构体变量用于nanosleep

    req.tv_sec = 1;                               // 设置睡眠秒数
    req.tv_nsec = (long)0.5 * 1000 * 1000 * 1000; // 设置睡眠纳秒数,这里为0.5秒的纳秒数

    printf("延时开始。\n"); // 输出提示信息
    nanosleep(&req, &rem);  // 执行睡眠操作,rem用于返回未睡眠完的时间(如果有)
    printf("延时完毕。\n"); // 睡眠结束后输出提示信息

    return 0; // 程序正常退出
}

  运行结果:

在这里插入图片描述

  • 30
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值