关键词搜索

源码搜索 ×
×

C 语言编程 — pthread 用户线程操作

发布2023-04-07浏览875次

详情内容

目录

pthread 线程库

pthread(POSIX Threads)是一套符合 POSIX(Portable Operating System Interface,可移植操作系统接口)的 User Thread 操作 API 标准,定义了一组线程相关的函数(60 多个)和数据类型。pthread API 可以用于不同的操作系统上,因此被称为可移植的线程 API。

在版本较新的 Linux Kernel 中,pthread API 的具体实现是 NPTL(Native POSIX Thread Library)。为了方便描述,在后文中我们使用 pthread 来统称。

  • 实现源码:https://github.com/lattera/glibc/blob/master/nptl/pthread_create.c
  • 文档:https://docs.oracle.com/cd/E19253-01/819-7051/attrib-74380/index.html

pthread 线程库围绕 struct pthread 提供了一系列的接口,用于完成 User Thread 创建、调度、销毁等一系列管理。

在这里插入图片描述

TCB 结构体

在 pthread 中,使用 TCB(Thread Control Block,线程控制块)来存储 User Thread 的所有信息,TCB 的体量会比 PCB 小非常多。对应的 pthread 结构体如下:

// glibc/nptl/descr.h

/* Thread descriptor data structure.  */
struct pthread {
    struct pthread *self;           // 指向自身的指针
    struct __pthread_internal_list *thread_list;  // 线程列表,指向线程列表的指针,用于实现线程池;
    void *(*start_routine)(void*);  // 线程的入口函数,由 pthread_create() 函数传入;
    void *arg;                      // 线程的入口函数参数,由 pthread_create() 函数传入;
    void *result;                   // 线程的返回值,由线程的入口函数返回;
    pthread_attr_t *attr;           // 线程的属性,包括栈保护区大小、调度策略等,由 pthread_create() 函数传入;
    pid_t tid;                      // 线程的唯一标识符,由 Kernel 分配;
    struct timespec *waiters;       // 等待的时间戳
    size_t guardsize;               // 栈保护区大小
    int sched_policy;               // 调度策略
    struct sched_param sched_params;// 调度参数
    void *specific_1stblock;        // 线程私有数据的第一个块
    struct __pthread_internal_slist __cleanup_stack;  // 清理函数栈
    struct __pthread_mutex_s *mutex_list;  // 线程持有的互斥锁列表
    struct __pthread_cond_s *cond_list;    // 线程等待的条件变量列表
    unsigned int detach_state:2;     // 线程分离状态,包括分离和未分离两种;
    unsigned int sched_priority:30;  // 线程的调度优先级
    unsigned int errno_val;          // 线程的错误码
};

    线程的生命周期管理

    线程的合并与分离

    对于 User Thread 的生命周期管理,首先要明确线程合并和线程分离的概念。

    线程的合并与分离是指在多线程程序中,对于已经创建的线程进行结束和回收资源的 2 种操作方式。

    • 线程的合并:指等待某个线程结束后,主线程再继续执行资源回收。
    • 线程的分离:指线程结束后会自动释放资源,不需要等待主线程回收。

    需要注意的是,线程的合并和分离操作都必须在目标线程执行结束之前进行,并且必须二选一,否则会导致内存泄露甚至崩溃。

    pthread_create() 创建线程

    函数作用:用于创建一条新的对等线程,并指定线程的入口函数和参数。pthread 库就会为 User Thread 分配 TCB、PC(程序计数器)、Registers(寄存器)和 Stack(栈)等资源。并将其加入到 Thread Queue 中等待执行。直到 User Thread 被调度到 CPU 时,开始执行线程入口函数。

    函数原型

    • thread 参数:是一个 pthread_t 类型指针,用于存储 TID。
    • attr 参数:是一个 pthread_attr_t 类型指针,用于指定线程的属性,通常为 NULL。
    • start_routine 参数:线程入口函数,是一个 void* 类型函数指针(或直接使用函数名)。线程入口函数必须是一个 static 静态函数或全局函数,因为 pthread 会把线程入口函数的返回值传递到 pthread_join() 中,所以需要能够找到它。
    • arg 参数:线程参数,是一个 void* 类型参数。
    • 函数返回值
      • 成功:返回 0;
      • 失败:返回 -1;
    int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                       void *(*start_routine) (void *), void *arg);
    
    • 1
    • 2

    pthread_join() 合并线程

    函数作用:执行线程合并。阻塞当前的主线程,直到指定线程执行结束,然后获得线程的执行结果,并释放线程的资源。

    函数原型

    • thread 参数:指定等待的 TID。
    • retval:是一个指向指针的指针类型,用于存储线程的结果返回。
    int pthread_join(pthread_t thread, void **retval);
    
    • 1

    在这里插入图片描述

    pthread_exit() 线程主动退出

    函数作用:线程主动终止自己,返回结果到 pthread_join()。需要注意的是,Main Thread 不应该调用 pthread_exit(),这样会退出整个 User Process。

    函数原型

    • retval:是一个指针类型,用于存储退出码。如果不需要返回值,则设置为 NULL。
    void pthread_exit(void *retval);
    
    • 1

    在这里插入图片描述

    pthread_detach() 分离线程

    函数作用:执行线程分离。将指定的线程标记为 “可分离的“,表示该线程在执行结束后会自动释放资源(由资源自动回收机制完成),无需等待主线程回收。另一方面,这也意味这主线程无法获得线程的返回值。

    函数原型

    • thread 参数:指定 TID。
    int pthread_detach(pthread_t thread);
    
    • 1

    线程的属性

    可以在 pthread_create() 新建线程时,直接指定线程的属性,也可以更改已经存在的线程的属性,包括:

    • 线程分离属性;
    • LWP 绑定属性;
    • CPU 亲和性属性;
    • 调度属性;
    • 等等。
    // 定义一个 pthread attribute 实例。
    pthread_attr_t attr;
    
    // 初始化一个 pthread attribute 实例。
    int pthread_attr_init(pthread_attr_t *attr);
    // 清除一个 pthread attribute 实例。
    int pthread_attr_destory(pthread_attr_t *attr);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    线程分离属性

    线程分离属性,即:将线程设定为 “可分离的"。

    函数原型

    • attr:指定一个 pthread attribute 实例。
    • detachstate:指定 attr 的分离属性:
      • PTHREAD_CREATE_DETACHED:指示线程是分离的。
      • PTHREAD_CREATE_JOINABLE:默认属性,指示线程是合并的,需要主线程调用 pthread_join() 来等待并释放资源。
    pthread_attr_setdetachstat(pthread_attr_t *attr, int detachstate);
    
    • 1

    设定属性后不需要再通过 pthread_detach() 重复设定。

    LWP 绑定属性

    POSIX 标准引入了 “线程竞争域“ 的概念,即:User Threads 对 CPU 资源发起竞争的范围,并要求至少要实现下列 2 种范围之一:

    1. PTHREAD_SCOPE_PROCESS:User Threads 在 User Process 范围内竞争 CPU 资源。
    2. PTHREAD_SCOPE_SYSTEM:User Threads 在 System 范围内竞争 CPU 资源。

    相应的,pthread API 库也提供了 pthread_attr_setscope() 接口来设定 User Threads 的竞争范围。但是,实际上 Linux NPTL 只实现了 PTHREAD_SCOPE_SYSTEM 这一种方式。

    具体而言就是 LWP(Light Weight Process)的实现。在还没有 pthread 线程库的早期版本的 Linux 中,只有 Kernel Thread 的概念,User Process 只能通过 kthread_crearte SCI(系统调用接口)来创建 Thread。但这种方式显然会存在 User Space(User Process)和 Kernel Space(Kernel Thread)之间频繁的切换。

    为了解决这个问题,POSIX 标准引入了 User Thread 和 LWP 的概念,最早在 Solaris 操作系统中实现。之所以要同时引入 LWP 的目的是为了让实现 User Thread 的 pthread API 接口能够在不同的操作系统中保持良好的兼容性。

    而 Linux NPTL 则将 LWP 作为 User Thread 和 Kernel Thread 之间建立映射关系的桥梁,并让 User Threads 能够竞争全局的 CPU 资源,以此来发挥多核处理器平台的并行优势。

    当调用 pthread_create() 新建多个 User Threads 时,Kernel 会为这些 User Threads 创建少量的 LWPs,并建立 M:N 的映射关系。这个映射过程是由 Kernel 完成的,开发者无法手动干预。

    在这里插入图片描述

    CPU 亲和性属性

    在 Kernel 中,LWP 同样作为可调度单元,与 kthread_create() 创建的 Kernel Thread 一般,可以被 Kernel Scheduler 识别并根据调度策略调度到不同的 CPU 上执行。

    默认情况下,User Thread 依靠 LWP 的可调度能力,会被 Kernel 尽力而为的分配到多个不同的 CPU cores 上执行,以达到负载均衡。但这种分配是随机的,不保证 User Thread 最终在那个 Core 上执行。

    相对的,可以通过修改 User Threads 的 CPU 亲和性属性让它们在指定的 cpuset 中竞争。

    函数原型

    • attr:指定一个 pthread attribute 实例。
    • cpusetsize:指示 cpuset 实例的大小。
    • cpuset:指示 cpuset 实例,通过 <sched.h> 中定义的函数进行初始化和操作。
    int pthread_attr_setaffinity_np(pthread_attr_t *attr, size_t cpusetsize, const cpu_set_t *cpuset);
    
    • 1

    调度属性

    User Thread 的调度属性有 3 类,分别是:调度算法、调度优先级、调度继承权。

    调度算法,函数原型

    • attr:指定一个 pthread attribute 实例。
    • policy:指定调度算法:
      • SCHED_OTHER:Linux 私有,默认采用,用于非实时应用程序。
      • SCHED_FIFO(先进先出):POSIX 标准,用于实时应用程序。
      • SCHED_RR(轮询):POSIX 标准,用于实时应用程序。
    int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
    
    • 1

    调度优先级,函数原型:只在 SCHED_FIFO 和 SCHED_RR 等实时调度算法中生效,User Process 需要以 root 权限运行,且需要显式放弃父线程的继承权。

    • attr:指定一个 pthread attribute 实例。
    • param:指向了一个 sched_param 结构体,其中 sched_priority 字段用于指定优先值,范围 1~99。
    struct sched_param {
    	int sched_priority;
    	char __opaque[__SCHED_PARAM_SIZE__]; 
    };
    
    int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    调度继承权,函数原型:子线程是否继承父线程的调度算法和调度优先级。

    • attr:指定一个 pthread attribute 实例。
    • inheritsched
      • PTHREAD_EXPLICIT_SCHED:不继承。
      • PTHREAD_INHERIT_SCHED:继承,默认。
    int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
    
    • 1

    多核平台并行编程示例

    实践多线程的目的往往在于提升应用程序的执行性能,通常有并发和并行这 2 种方式:

    1. 并发程序:并发指在同一时间段内,多线程在同一个 CPU 上执行。并发程序不强制要求 CPU 具备多核计算能力,只要求多个线程在同一个 Core 上进行 “分时轮询” 处理,以此在宏观上实现多线程同时执行的效果。并发程序的执行通常是不确定的,这种不确定性来源于资源之间的相关依赖和竞态条件,可能导致执行的线程间相互等待(阻塞)。并发程序通常是有状态的(非幂等性)。

    2. 并行程序:并行指在同一时刻内,多线程在不同的 CPU core 上同时执行。并行程序会强制要求 CPU 具备多核计算能力,并行程序的每个执行模块在逻辑上都是独立的,即线程执行时可以独立地完成任务,从而做到同一时刻多个指令能够同时执行。并行程序通常是无状态的(幂等性)。

    示例程序:

    #define _GNU_SOURCE // 用于启用一些非标准的、GNU C 库扩展的特性,例如:<sched.h> 中的 CPU_ZERO 和 CPU_SET 函数。
    
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <sched.h>
    
    #define THREAD_COUNT 12  // 12 个对等线程
    
    void show_thread_sched_policy_and_cpu(int threadno)
    {
        int cpuid;
        int policy;
        struct sched_param param;
    
        cpuid = sched_getcpu();
        pthread_getschedparam(pthread_self(), &policy, &param);
    
        printf("Thread %d is running on CPU %d, ", threadno, cpuid);
        switch (policy)
        {
        case SCHED_OTHER:
            printf("SCHED_OTHER\n", threadno);
            break;
        case SCHED_RR:
            printf("SCHDE_RR\n", threadno);
            break;
        case SCHED_FIFO:
            printf("SCHED_FIFO\n", threadno);
            break;
        default:
            printf("UNKNOWN\n");
        }
    }
    
    void *thread_func(void *arg)
    {
        int i, j;
        long threadno = (long)arg;
        printf("thread %d start\n", threadno);
    
        sleep(1);
        show_thread_sched_policy_and_cpu(threadno);
    
        for (i = 0; i < 10; ++i)
        {
            // 适当调整执行时长
            for (j = 0; j < 10000000000; ++j)
            {
            }
        }
    
        printf("thread %d exit\n", threadno);
        return NULL;
    }
    
    int main(int argc, char *argv[])
    {
        long i;
        int cpuid;
        cpu_set_t cpuset;
        pthread_attr_t attr[THREAD_COUNT];
        pthread_t pth[THREAD_COUNT];
        struct sched_param param;
    
        // 初始化线程属性
        for (i = 0; i < THREAD_COUNT; ++i)
            pthread_attr_init(&attr[i]);
    
        // 调度属性设置
        for (i = 0; i < THREAD_COUNT / 2; ++i)
        {
            param.sched_priority = 10;
            pthread_attr_setschedpolicy(&attr[i], SCHED_FIFO);
            pthread_attr_setschedparam(&attr[i], &param);
            pthread_attr_setinheritsched(&attr[i], PTHREAD_EXPLICIT_SCHED);
        }
    
        for (i = THREAD_COUNT / 2; i < THREAD_COUNT; ++i)
        {
            param.sched_priority = 20;
            pthread_attr_setschedpolicy(&attr[i], SCHED_RR);
            pthread_attr_setschedparam(&attr[i], &param);
            pthread_attr_setinheritsched(&attr[i], PTHREAD_EXPLICIT_SCHED);
        }
    
        // CPU 亲和性属性设置,使用 cpuset(0,1)。
        for (i = 0; i < THREAD_COUNT; ++i)
        {
            pthread_create(&pth[i], &attr[i], thread_func, (void *)i);
    
            CPU_ZERO(&cpuset);
            cpuid = i % 2;
            CPU_SET(cpuid, &cpuset);
            pthread_setaffinity_np(pth[i], sizeof(cpu_set_t), &cpuset);
        }
    
        for (i = 0; i < THREAD_COUNT; ++i)
            pthread_join(pth[i], NULL);
    
        // 清理线程属性
        for (i = 0; i < THREAD_COUNT; ++i)
            pthread_attr_destroy(&attr[i]);
    
        return 0;
    }
    
      24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • CPU 调度亲和性效果。
      在这里插入图片描述

    • User Thread 和 LWP(12 + 1)绑定关系与调度策略效果。

    $ ps -eLo pid,ppid,tid,lwp,nlwp,class,rtprio,ni,pri,psr,pcpu,policy,stat,comm | awk '$7 !~ /-/{print $0}'
    
      PID  PPID   TID   LWP NLWP CLS RTPRIO  NI PRI PSR %CPU POL STAT COMMAND
    26031 24641 26032 26032   13 FF      10   -  50   0  0.0 FF  Rl+  test1
    26031 24641 26033 26033   13 FF      10   -  50   1  0.0 FF  Rl+  test1
    26031 24641 26034 26034   13 FF      10   -  50   0  0.0 FF  Rl+  test1
    26031 24641 26035 26035   13 FF      10   -  50   1  0.0 FF  Rl+  test1
    26031 24641 26036 26036   13 FF      10   -  50   0  0.0 FF  Rl+  test1
    26031 24641 26037 26037   13 FF      10   -  50   1  0.0 FF  Rl+  test1
    26031 24641 26038 26038   13 RR      20   -  60   0 33.3 RR  Rl+  test1
    26031 24641 26039 26039   13 RR      20   -  60   1 33.3 RR  Rl+  test1
    26031 24641 26040 26040   13 RR      20   -  60   0 33.3 RR  Rl+  test1
    26031 24641 26041 26041   13 RR      20   -  60   1 33.2 RR  Rl+  test1
    26031 24641 26042 26042   13 RR      20   -  60   0 33.2 RR  Rl+  test1
    26031 24641 26043 26043   13 RR      20   -  60   1 33.3 RR  Rl+  test1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • PID:进程 ID,唯一标识一个进程。
    • PPID:父进程 ID,标识当前进程的父进程。
    • TID:线程 ID,标识一个线程,与 LWP ID 一致(一一对应)。
    • LWP:LWP ID,是内核调度实体,多个 LWP 可以属于同一个进程,但它们的调度是相互独立的。
    • NLWP:进程的 LWP 数。
    • CLS:调度算法。
    • RTPRIO:实时优先级,仅适用于实时调度策略。
    • NI:Nice 值,越小表示越高的优先级。
    • PRI:优先级,与 NI 的值相关。
    • PSR:进程或线程所绑定的处理器编号。
    • %CPU:进程或线程的 CPU 使用率。
    • POL:进程或线程的调度策略。
    • STAT:进程或线程的状态。
    • COMMAND:进程或线程的命令名或可执行文件名。

    多线程安全与多线程同步

    多线程安全(Multi-Thread Safe),就是在多线程环境中,多个线程在同一时刻对同一份共享数据(Shared Resource,e.g. 寄存器、内存空间、全局变量、静态变量 etc.)进行写操作(读操作不会涉及线程安全的问题)时,不会出现数据不一致。

    为了确保在多线程安全,就要确保数据的一致性,即:线程安全检查。多线程之间通过需要进行同步通信,以此来保证共享数据的一致性。

    pthread 库提供了保证线程安全的方式:

    1. 互斥锁(Mutex):是一种线程安全机制,为共享数据加上一把锁,拥有锁的线程,才可以访问共享数据。以此保护共享数据不被多个线程同时访问。
    2. 条件变量(Condition Variable):是一种线程同步机制,用于判断线程是否满足了特定的竞争条件(Race Condition)。只有满足条件的线程,才可以获得互斥锁,以此来避免死锁的情况。

    需要注意的是,线程安全检查的实现会带来一定的系统开销。

    在这里插入图片描述

    互斥锁(Mutex)

    pthread_mutex_init()

    函数作用:用于初始化一个互斥锁实体。

    函数原型

    • mutex 参数:pthread_mutex_t 类型指针,用于指定要初始化的互斥锁。
    • attr 参数:pthread_mutexattr_t 类型指针,用于指定互斥锁的属性,例如:递归锁、非递归锁等,通常为 NULL。
    int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
    
    • 1

    pthread_mutex_lock()

    函数作用:User Thread 用于获取互斥锁。如果互斥锁已被 Other User Thread 获得,则当前 User Thread 会阻塞。

    函数原型

    • mutex 参数:pthread_mutex_t 类型指针,用于指定要获取的互斥锁。
    int pthread_mutex_lock(pthread_mutex_t *mutex);
    
    • 1

    pthread_mutex_unlock()

    函数作用:User Thread 用于释放互斥锁,互斥锁重回可用状态。如果当前 User Thread 并没有锁,则该函数可能会产生未定义行为。

    函数原型

    • mutex 参数:pthread_mutex_t 类型指针,用于指定要释放的互斥锁。
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
    
    • 1

    条件变量(Condition Variable)

    互斥锁和条件变量都是多线程同步的工具,但是它们的作用不同:

    • 互斥:互斥锁可以保护共享资源的访问,防止多个线程同时修改共享资源,但是它无法告知其他线程何时可以安全地访问共享资源,有可能导致死锁的发生。

    举例来说,存在全局变量 n(共享数据)被多线程访问。当 TA 获得锁后,在临界区中访问 n,且只有当 n > 0 时,才会释放锁。这意味着当 n == 0 时,TA 将永远不会释放锁,从而造成死锁。

    在这里插入图片描述

    那么解决死锁的方法,就是设定一个条件:只有当 n > 时,TA 才可以获得锁。而这个条件,就是多线程之间需要同步的信息。即:在多线程环境中,当一个线程需要等待某个条件成立时,才可以获得锁,那么应该使用条件变量来实现。

    • 同步:pthread 条件变量提供了一种线程同步机制,当特定的事件发生时,它可以唤醒一个或多个在等待事件的线程,从而实现线程间的同步和协调。条件变量通常与互斥锁一起使用,以避免竞态条件和死锁的发生。

    pthread_cond_init()

    函数作用:用于初始化一个条件变量实体。

    函数原型

    • cond 参数:pthread_cond_t 类型指针,用于指定要初始化的条件变量。
    • attr 参数:pthread_condattr_t 类型指针,用于指定条件变量的属性,通常为 NULL。
    int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
    
    • 1

    pthread_cond_wait()

    函数作用:线程用于等待某个条件变量满足。

    1. 当 T1 线程调用 pthread_cond_wait() 时,会自动地释放掉互斥锁,并阻塞线程,开始等待。
    2. 直到另一个 T2 线程调用了 pthread_cond_signal() 或 pthread_cond_broadcast(),以此来通知 T1 条件变量满足了。
    3. 然后 T1 pthread_cond_wait() 重新获取指定的互斥锁并返回。

    在这里插入图片描述

    函数原型

    • cond 参数:pthread_cond_t 类型指针,用于指定要等待的条件变量。
    • mutex 参数:pthread_mutex_t 类型指针,用于指定要关联的互斥锁。在等待期间,线程将释放该互斥锁。
    int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
    
    • 1

    pthread_cond_signal()

    函数作用:用于向等待条件变量的线程发送信号,唤醒其中的一个线程。

    函数原型

    • cond 参数:pthread_cond_t 类型指针,用于指定要发送信号的条件变量。
    int pthread_cond_signal(pthread_cond_t *cond);
    
    • 1

    pthread_cond_broadcast()

    函数作用:用于向等待条件变量的所有线程发送信号,唤醒所有等待的线程。

    函数原型

    • cond 参数:pthread_cond_t 类型指针,用于指定要发送信号的条件变量。
    int pthread_cond_broadcast(pthread_cond_t *cond);
    
    • 1

    互斥锁和条件变量配合使用

    当一个线程需要某个条件成立后才可以访问共享数据时。需要先锁定一个互斥锁,然后检查条件变量,如果条件不满足,则需要挂起并等待。

    在这里插入图片描述

    在这里插入图片描述

    #include <pthread.h>
    
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    
    int data = 0;  // 共享数据
    
    void *producer(void *arg)
    {
        for (int i = 0; i < 10; i++) {
            pthread_mutex_lock(&mutex); // 加锁
            data++; // 修改共享数据
            pthread_cond_signal(&cond); // 发送信号
            pthread_mutex_unlock(&mutex); // 解锁
            sleep(1);
        }
        pthread_exit(NULL);
    }
    
    void *consumer(void *arg)
    {
        while (1) {
            pthread_mutex_lock(&mutex); // 加锁
            while (data == 0) { // 如果没有数据就等待信号
                pthread_cond_wait(&cond, &mutex);
            }
            printf("data = %d\n", data); // 打印共享数据
            data--; // 修改共享数据
            pthread_mutex_unlock(&mutex); // 解锁
            sleep(1);
        }
        pthread_exit(NULL);
    }
    
    int main()
    {
        pthread_t tid1, tid2;
    
        pthread_create(&tid1, NULL, producer, NULL);
        pthread_create(&tid2, NULL, consumer, NULL);
    
        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL);
    
        return 0;
    }
    
      24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    线程非安全标准库函数

    C 语言提供的大部分标准库函数都是线程安全的,但是也有几个常用函数是线程不安全的,也称为不可重入函数,原因是使用了某些全局或者静态变量。

    我们知道,全局变量和静态变量分别对应内存中的全局变量区和静态存储区,这些区域都是可以跨线程访问的。在多线程环境中,这些数据如果在没有加锁的情况下并行读写,就会造成 Segmentfault / CoreDump 之类的问题。

    • 不可重入函数汇总:
      在这里插入图片描述

    相关技术文章

    点击QQ咨询
    开通会员
    返回顶部
    ×
    微信扫码支付
    微信扫码支付
    确定支付下载
    请使用微信描二维码支付
    ×

    提示信息

    ×

    选择支付方式

    • 微信支付
    • 支付宝付款
    确定支付下载