美文网首页
多线程编程(pthread)

多线程编程(pthread)

作者: 给艺艺一个未来 | 来源:发表于2021-12-12 20:34 被阅读0次

简介

pthread - POSIX threads 。
POSIX 是IEEE为在各种UNIX操作系统上运行软件,而定义API的一系列互相关联的标准的总称,其正式称呼为IEEE Std 1003,而国际标准名称为 ISO/IEC 9945 。
因此,对于 pthread, 有其标准,也有其实现,实现相对于标准会存在一定的出入,这也体现在后文的介绍中。

POSIX.1 中的定义

POSIX.1 为 POSIX threads 或 Pthreads 的线程编程指定了一组接口(函数、头文件)。 一个进程可以包含多个线程,所有线程都在执行同一个程序。 这些线程共享相同的全局内存(数据段和堆段),但每个线程都有自己的堆栈(自动变量)。

POSIX.1 要求线程共享一系列其他属性:

  • parent process ID -
  • process group ID and session ID
  • controlling terminal
  • user and group IDs
  • open file descriptors
  • record locks
  • signal dispositions
  • file mode creation mask
  • current directory and root directory
  • interval timers and POSIX timers
  • nice value
  • resource limits
  • measurements of the consumption of CPU time and resources

除了 stack 之外,POSIX.1 还指定了以下属性对于每个线程都是不同的、独立的:

  • thread ID ( 数据类型是:pthread_t)
  • signal mask
  • the errno variable
  • alternate signal stack
  • real-time scheduling policy and priority

下列的Linux特性也是每个线程独有的:

  • capabilities
  • CPU affinity

POSIX Thread 的 Linux 实现

随着时间的推移,Linux 上的 GNU C 库提供了两个线程实现 :

  • LinuxThreads
  • NPTL (Native POSIX Threads Library)

LinuxThreads

这是原始的 Pthreads 实现。 从 glibc2.4 开始,不再支持此实现。
LinuxThreads 实现在许多方面偏离了 POSIX.1 规范,因此我们暂不讨论 LinuxThreads,除非我们使用 的 glibc 是低于 2.4 的版本。

NPTL

这是现代 Pthreads 实现。 与 LinuxThreads 相比,NPTL 更符合 POSIX.1 规范的要求,并且在创建大量线程时具有更好的性能。 NPTL 从 glibc 2.3.2 开始可用,并且需要 Linux 2.6 内核中存在的功能。

在 NPTL 中:

  • 一个进程中的所有线程都放在同一个线程组中。
  • 线程组的所有成员共享相同的 PID。
  • 不使用 manager thread。

NPTL 仍至少有一项不符合 POSIX.1:

  • 线程不共享同一个 nice value

NPTL 一些不符合 POSIX.1 发生在较旧的内核:

  • times(2) 和 getrusage(2) 返回的信息是每个线程的而不是进程范围的(在内核 2.6.9 中修复)。
  • 线程不共享 resource limits(在内核 2.6.10 中修复)。
  • 线程不共享 interval timers(在内核 2.6.12 中修复)。
  • 只允许主线程使用 setsid 启动新 session (在内核 2.6.16 中修复)。
  • 只允许主线程使用 setpgid 使进程成为进程组 leader (在内核 2.6.16 中修复)。
  • 线程具有不同的备用信号堆栈设置。 但是,新线程的备用信号堆栈设置是从创建它的线程复制的,因此线程最初共享备用信号堆栈(在内核 2.6.16 中修复)。

请注意以下有关 NPTL 实现的进一步要点:

  • 如果 stack size soft resource limit 设置为 unlimited 以外的值,则该值定义新线程的默认堆栈大小。 为了有效,这个限制必须在程序执行之前设置。
    • 可以使用 ulimit -s (shell 内置命令)进行设置。
    • 也可以在程序代码中使用 setrlimit 设置 RLIMIT_STACK 的 大小。

pthread 编程

头文件
#include <pthread.h>

pthread_t

typedef unsigned long int pthread_t;
  • 用于声明线程ID、标识线程

pthread_create

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

参数:

  • pthread_t :线程 ID
  • pthread_attr_t :线程属性
  • start_routine :函数指针,该函数的返回值类型为 void,该函数的参数类型为 void
  • arg :将作为 start_routine 的参数

返回值:

  • EAGAIN : 资源不足,遇到系统对资源施加的限制,无法创建另一个线程。 有许多限制可能会触发此错误:1. 已达到 RLIMIT_NPROC 软资源限制(通过 setrlimit 设置,通过 getrlimit 进行查询),该限制限制了真实用户 ID 的进程和线程数; 2. 已达到内核对进程和线程数的系统范围限制,/proc/sys/kernel/threads-max; 3. 达到了 PID 的最大量,/proc/sys/kernel/pid_max;4. 其他资源:如 打开的最大文件描述符数量,vm.max_map_count 等。
  • EINVAL : attr 中的设置无效。
  • EPERM : 没有权限设置attr 中指定的调度策略和参数。

注:在创建大量线程时,通常易发生 EAGAIN 错误

pthread_attr_t

typedef struct
{
       int                               __detachstate;   // 线程的分离状态
       int                               __schedpolicy;   // 线程调度策略
       struct sched_param                __schedparam;    // 线程的调度参数
       int                               __inheritsched;  // 线程的继承性
       int                               __scope;         // 线程的作用域
       size_t                            __guardsize;     // 线程栈末尾的警戒缓冲区大小
       int                               __stackaddr_set; // 线程的栈设置
       void *                            __stackaddr;     // 线程堆栈的位置
       size_t                            __stacksize;     // 线程栈的大小
}pthread_attr_t;

修改 pthread_attr_t 的接口:
pthread_attr_init
pthread_attr_destroy

pthread_attr_setaffinity_np(3)
pthread_attr_setdetachstate(3)
pthread_attr_setschedpolicy(3)
pthread_attr_setinheritsched(3)
pthread_attr_setschedparam(3)
pthread_attr_setscope(3)
pthread_attr_setguardsize(3)
pthread_attr_setstack(3)
pthread_attr_setstackaddr(3)
pthread_attr_setstacksize(3)

pthread_cancel
向线程发送取消请求

int pthread_cancel(pthread_t thread);

向线程发送取消请求。
目标线程是否以及何时对取消请求做出反应取决于该线程控制下的两个属性:可取消性状态(enabled (the default for new threads) or disabled)和可取消性类型(asynchronous or deferred (the default for new threads))。

  • 可取消性状态:如果线程 disable 取消,则取消请求将保持排队状态,直到该线程 enable取消。如果线程 enable 取消,则根据其可取消性类型确定何时发生取消。
  • 可取消性类型:asynchronous 意味着线程可以在任何时刻被取消。deferred 意味着取消将被延迟,直到线程下一次调用作为取消点的函数。

当一个取消请求被发起时, 线程中将会进入以下步骤:

  1. 弹出并调用取消性清理处理程序。

  2. Thread-specific 的数据的析构函数将以 unspecified 的顺序被调用。

  3. 线程终止。

pthread_join

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

pthread_join() 函数等待 thread 指定的线程终止。 如果该线程已经终止,则 pthread_join() 立即返回。 pthread_join() 指定的线程必须是 joinable 的。

如果 retval 不为 NULL,则 pthread_join() 将目标线程的退出状态(即目标线程提供给 pthread_exit 的值)复制到 retval 指向的位置。 如果目标线程被取消,则 PTHREAD_CANCELED 被放置在 retval 指向的位置。

如果多个线程同时尝试 join 同一个线程,结果是不确定的。 如果调用 pthread_join() 的线程被取消,则目标线程将保持 joinable。

pthread_detach

int pthread_detach(pthread_t thread);

pthread_detach() 函数将线程标识的线程标记为已分离。 当一个分离的线程终止时,它的资源会自动释放回系统,而无需另一个线程 join 被终止的线程。

尝试分离已分离的线程会导致未指定的行为。

返回值:

  • EINVAL :thread 不是 joinable thread。
  • ESRCH :找不到线程ID所标识的线程。

pthread_exit

void pthread_exit(void *retval);

终止调用线程。
pthread_exit() 函数终止调用线程并通过 retval 返回一个值(如果该线程可连接),同一进程中的另一个线程可调用 pthread_join 获取该返回值。

pthread_kill

 #include <signal.h>

int pthread_kill(pthread_t thread, int sig);

向线程发送信号。
pthread_kill() 函数将信号 sig 发送到线程,该线程与调用者处于同一进程中。 信号异步定向到线程。如果 sig 为 0,则不发送信号,但仍会执行错误检查。

pthread_yield

int pthread_yield(void);

主动让出执行权,主动让出CPU时间。

pthread_self

pthread_t pthread_self(void);

获取调用者的线程ID。

pthread_setaffinity_np

 int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize,
                                  const cpu_set_t *cpuset);
 int pthread_getaffinity_np(pthread_t thread, size_t cpusetsize,
                                  cpu_set_t *cpuset);

pthread_setaffinity_np 设置线程和CPU的亲和性。
pthread_getaffinity_np 获取线程和CPU的亲和性。

cpu_set_t 的常用接口:

#include <sched.h>
void CPU_ZERO(cpu_set_t *set);           // Clears set, so that it contains no CPUs.
void CPU_SET(int cpu, cpu_set_t *set);   // Add CPU cpu to set.
void CPU_CLR(int cpu, cpu_set_t *set);   // Remove CPU cpu from set.
int  CPU_ISSET(int cpu, cpu_set_t *set); // Test to see if CPU cpu is a member of set.

int cpu 表示第 cpu 号 cpu,可以从 /proc/cpuinfo 中查看 cpu 号(processor),通常是从 0 ~ N
更多接口见:CPU_SET

pthread_setname_np

int pthread_setname_np(pthread_t thread, const char *name);
int pthread_getname_np(pthread_t thread, char *name, size_t len);

pthread_setname_np 函数可用于为线程设置唯一名称,这对于调试多线程应用程序非常有用。
pthread_getname_np 函数可用于获取线程的名称。

pthread_mutex_t

互斥量:用于控制对资源的访问

int pthread_mutex_init(pthread_mutex_t  *mutex,
           const pthread_mutexattr_t *attr);

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 等价于 pthread_mutex_init(&mutex, NULL);

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

int pthread_mutex_destroy(pthread_mutex_t *mutex);

pthread_mutexattr_t

  • pthread_mutexattr_setpshared - 设置互斥锁范围设置互斥锁的强健属性
  • pthread_mutexattr_getpshared - 获取互斥锁范围
  • pthread_mutexattr_settype - 设置互斥锁的类型属性
  • pthread_mutexattr_gettype - 获取互斥锁的类型属性
  • pthread_mutexattr_setprotocol - 设置互斥锁属性的协议
  • pthread_mutexattr_getprotocol - 获取互斥锁属性的协议
  • pthread_mutexattr_setprioceiling - 设置互斥锁属性的优先级上限
  • pthread_mutexattr_getprioceiling - 获取互斥锁属性的优先级上限
  • pthread_mutex_setprioceiling - 设置互斥锁的优先级上限
  • pthread_mutex_getprioceiling - 获取互斥锁的优先级上限
  • pthread_mutexattr_setrobust_np - 设置互斥锁的强健属性
  • pthread_mutexattr_getrobust_np - 获取互斥锁的强健属性

pthread_mutexattr_settype

  • PTHREAD_MUTEX_TIMED_NP:当一个线程加锁后,其余请求锁的线程形成等待队列,在解锁后按优先级获得锁。 (默认值
  • PTHREAD_MUTEX_ADAPTIVE_NP:动作最简单的锁类型,解锁后所有线程重新竞争。
  • PTHREAD_MUTEX_RECURSIVE_NP:允许同一线程对同一锁成功获得多次(递归锁)。当然也要解锁多次。其余线程在解锁时重新竞争。
  • PTHREAD_MUTEX_ERRORCHECK_NP:若同一线程请求同一锁,返回EDEADLK(进行死锁检测,返回死锁的错误,避免死锁),否则与PTHREAD_MUTEX_TIMED_NP动作相同(直接死锁)。
pthread_cond_t

条件变量:用于线程间同步

int pthread_cond_init(pthread_cond_t *cond,
           const pthread_condattr_t *attr);

pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 等价于 pthread_cond_init(&cond, NULL);

int pthread_cond_wait(pthread_cond_t *cond,
           pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond,
           pthread_mutex_t *mutex,
           const struct timespec *abstime);

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);

int pthread_cond_destroy(pthread_cond_t *cond);

pthread_condattr_t

  • pthread_condattr_setshared - 设置条件变量的进程共享属性
  • pthread_condattr_getshared - 获取条件变量的进程共享属性
  • pthread_condattr_setclock - 此函数用于设置pthread_cond_timewait函数使用的时钟ID
  • pthread_condattr_getclock - 此函数获取可被用于pthread_cond_timedwait函数的时钟ID。

相关文章

  • 多线程pthread_create的参数

    多线程编程 C语言使用pthread_create()函数完成多线程的创建,pthread_create()函数共...

  • 多线程编程(pthread)

    简介 pthread - POSIX threads 。POSIX 是IEEE为在各种UNIX操作系统上运行软件,...

  • 2018-03-20

    多线程的学习记录 1.pthread学习(pthread 属于POSIX 多线程开发框架) NSString* s...

  • 基础语法-多线程

    多线程的实现方案:pthread / NSThread / GCD / NSOperation pthread 基...

  • iOS多线程

    多线程方案对比 技术方案简介编程语言线程生命周期使用频率pthread一套通用的多线程API适用于Unix、Lin...

  • iOS GCD多线程实现并发上传多个本地文件

    多线程编程是软件工程是必备的基础技能之一,对于iOS开发,多线程的技术有很多,有最原始的pthread,面向对象的...

  • 多线程

    多线程的实现四种基本实现方案 多线程的实现方案.png pthread import

  • pthread多线程(C语言) + Socket

    pthread多线程(C语言) + Socket pthread是使用使用C语言编写的多线程的API, 简称Pth...

  • 关于多线程(NSThread介绍)

    通常在iOS中会遇到四种多线程编程的技术,分别是:(一)pthread(二)NSThread(三)NSOperat...

  • 02 多线程——Pthread

    多线程——Pthread 前言 Pthread线程 (POSIX threads),简称Pthreads,是线程的...

网友评论

      本文标题:多线程编程(pthread)

      本文链接:https://www.haomeiwen.com/subject/bsnkfrtx.html