pthread

pthread基础

关键词


线程、互斥量与锁

pthread_t,pthread_self, pthread_create ,pthread_mutex_init,pthread_mutex_lock


线程:

线程ID的类型是: pthread_t,是一个结构体数据类型,所以可移植操作系统实现不能把它作为整数处理。因此必须使用一个函数对两个线程ID进行比较:

#include <pthread.h>

int pthread_equal(pthread_t tid1, pthread_t tid2);// 若相等,返回非0数值;否则,返回0

锁类型的结构:ptread_mutex_t

linux下为了多线程同步,通常用到锁的概念。

posix下抽象了一个锁类型的结构:ptread_mutex_t。通过对该结构的操作,来判断资源是否可以访问。顾名思义,加锁(lock)后,别人就无法打开,只有当锁没有关闭(unlock)的时候才能访问资源。

它主要用如下5个函数进行操作。

1:pthread_mutex_init(pthread_mutex_t * mutex,const pthread_mutexattr_t *attr);

初始化锁变量mutex。attr为锁属性,NULL值为默认属性。

2:pthread_mutex_lock(pthread_mutex_t *mutex);加锁

3:pthread_mutex_tylock(pthread_mutex_t *mutex);加锁,但是与2不一样的是当锁已经在使用的时候,返回为EBUSY,而不是挂起等待。

4:pthread_mutex_unlock(pthread_mutex_t *mutex);释放锁

5:pthread_mutex_destroy(pthread_mutex_t *mutex);使用完后释放


pthread_self

使用 pthread_t 数据类型的 后果是不能用一种可移植的方式打印该数据类型的值。在程序调试中打印线程ID是非常有用的,而在其他情况下通常不需要打印线程ID。最坏的情况是有可能出现不可移植的调试代码,当然这也算不上是很大的局限性。

#include <pthread.h>

pthread_t pthread_self(void); // 返回调用线程的线程ID

线程创建

在传统的UNIX进程模型中,每个线程只有一个控制线程。从概念上讲,这与给予线程的模型中每个进程只含有一个线程是相同的。在POSIX线程(pthread)的情况下,程序开始运行时,它是以单进程中的单个控制线程启动的。在创建多个控制线程以前,程序的行为与传统的进程并没有什么区别。新增的线程可以通过调用 pthread_create 函数创建。

#include <pthread.h>

int pthread_create(pthread_t *restrict tidp,

const pthread_attr_t *restrict attr,

void *(*start_rtn)(void *),

void *restrict arg);

// 返回值:成功返回0;否则返回错误编号

  • tidp:新创建的线程ID会被设置成tidp指向的内存单元。
  • attr:用于定制各种不能的线程属性,默认为NULL
  • start_rtn:新创建的线程从start_rtn函数的地址开始运行,该函数只有一个void类型的指针参数即arg,如果start_rtn需要多个参数,可以将参数放入一个结构中,然后将结构的地址作为arg传入。


注意:

线程创建时并不能保证哪个线程会先运行:是新创建的线程,还是调用线程。新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的挂起信号集会被清楚。

pthread函数在调用失败时通常会返回错误代码,他们并不像其他的 POSIX 函数一样设置 errno。每个线程都提供 errno 的副本,这只是为了与使用 errno 的现有函数兼容。在线程中,从函数中返回错误代码更为清晰整洁,不需要依赖那些随着函数执行不断变化的全局状态,这样可以把错误的方位限制在引起出错的函数中。


互斥量

mutex
互斥量;互斥体;互斥;互斥锁;互斥对象


  • 可以使用 pthread 的互斥接口来保护数据,确保同一时间只有一个线程访问数据.
  • 互斥量(mutex)本质上是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量.
  • 对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程都会被阻塞直到当前线程释放该互斥锁.
  • 如果释放互斥量时有一个以上的线程阻塞,那么所有该锁上的阻塞线程都会变成可执行状态,第一个运行的线程就可以对互斥量加锁,其他线程就会看到互斥量依然锁着,只能回去再次等待它重新变为可用.
  • 这种方式下,每次只有一个线程可以向前执行.

互斥量是用 pthread_mutex_t 数据类型表示.在使用之前需要进行初始化,可以把它设置为常量 PTHREAD_MUTEX_INITIALIZER (只适用于静态分配的互斥量),也可以通过调用 pthread_mutex_init 函数进行初始化.如果动态分配互斥量,在释放内存前需要调用 pthread_mutex_destory.

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *restrict mutex,

const pthread_mutexattr_t *restrict attr); // 默认 attr = NULL

int pthread_mutex_destory(pthread_mutex_t *mutex);

// 返回值:成功返回0,否则返回错编号

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex); // 不希望被阻塞

int pthread_mutex_unlock(pthread_mutex_t *mutex);


// 返回值:成功返回0,否则返回错误编号

注:

在进行 Posix thread 编程时,出现以下编译错误:
error: expected expression before ‘{’ token
出现错误的语句为 products.lock = PTHREAD_MUTEX_INITIALIZER;


查了一下,在 pthread.h 中, PTHREAD_MUTEX_INITIALIZER 定义为:
# define PTHREAD_MUTEX_INITIALIZER \
{ { 0, 0, 0, 0, 0, 0, { 0, 0 } } }

原因应该比较清楚了,结构体的初始化问题。PTHREAD_MUTEX_INITIALIZER 用在静态类型的互斥量中,而且应该在互斥量定义的时候就用 PTHREAD_MUTEX_INITIALIZER 进行初始化,否则用 pthread_mutex_init 进行初始化。

pthread_mutex_t mutex;

pthread_mutex_init(&mutex,NULL); //NULL为默认的互斥锁



避免死锁

  • 如果对一个互斥量加锁两次,那么它自身就会陷入死锁状态.
  • 程序中使用一个以上的互斥量时,如果允许一个线程一直占有第一个互斥量时,并且在试图锁住第二个互斥量时处于阻塞状态,但是拥有第二个互斥量的线程也在试图锁住第一个互斥量.因为两个线程都在相互请求另一个线程拥有的资源,所以两个线程都无法向前运行,于是产生死锁.

pthread_mutex_timedlock


int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
const struct timespec *restrict tsptr); // 超时返回 ETIMEOUT

// 返回值:成功返回0,否则返回错误编号.


线程属性pthread_attr_t,pthread_attr_init,pthread_attr_getdetachstate


线程属性pthread_attr_t,pthread_attr_init,pthread_attr_getdetachstate



1. 线程属性:
使用pthread_attr_t类型表示,我们需要对此结构体进行初始化,

初始化后使用,使用后还要进行去除初始化!
pthread_attr_init:初始化
pthread_attr_destory:去除初始化

#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
若成功返回0,若失败返回-1。

pthread_attr_init之后,pthread_t结构所包含的内容就是操作系统实现
支持的线程所有属性的默认值。

如果pthread_attr_init实现时为属性对象分配了动态内存空间,
pthread_attr_destroy还会用无效的值初始化属性对象,因此如果经
pthread_attr_destroy去除初始化之后的pthread_attr_t结构被
pthread_create函数调用,将会导致其返回错误。

线程属性结构如下:

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;

下面主要讨论此结构体!!!

2. 分离状态:
线程的分离状态决定一个线程以什么样的方式来终止自己。

我们已经在前面已经知道,在默认情况下线程是非分离状态的,这种情况
下,原有的线程等待创建的线程结束。只有当pthread_join() 函数返回
时,创建的线程才算终止,才能释放自己占用的系统资源。

分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,
马上释放系统资源。

通俗的说也就是:我们知道一般我们要等待(pthread_join)一个线程的结束,
主要是想知道它的结束状态,否则等待一般是没有什么意义的!但是if有一
些线程的终止态我们压根就不想知道,那么就可以使用“分离”属性,那么我
们就无须等待管理,只要线程自己结束了,自己释放src就可以咯!这样更
方便!

#include <pthread.h>
int pthread_attr_getdetachstate(const pthread_attr_t * attr, int * detachstate);
int pthread_attr_setdetachstate(pthread_attr_t * attr, int detachstate);
参数:attr:线程属性变量
detachstate:分离状态属性
若成功返回0,若失败返回-1。

设置的时候可以有两种选择:
<1>.detachstate参数为:PTHREAD_CREATE_DETACHED 分离状态启动
<2>.detachstate参数为:PTHREAD_CREATE_JOINABLE 正常启动线程

3. 线程的继承性:

函数pthread_attr_setinheritsched和pthread_attr_getinheritsched分别用来设
置和得到线程的继承性!

#include <pthread.h>
int pthread_attr_getinheritsched(const pthread_attr_t *attr,int *inheritsched);
int pthread_attr_setinheritsched(pthread_attr_t *attr,int inheritsched);
参数:
attr 线程属性变量
inheritsched 线程的继承性
若成功返回0,若失败返回-1。

请注意:
继承性决定调度的参数是从创建的进程中继承还是使用在
schedpolicy和schedparam属性中显式设置的调度信息。

线程没有默认的继承值设置,所以如果关心线程的调度策略和参数,
只能手动设置!

可设置参数:
PTHREAD_INHERIT_SCHED: 新的线程继承创建线程的策略和参数!
PTHREAD_EXPLICIT_SCHED:新的线程继承策略和参数来自于
schedpolicy和schedparam属性中显式
设置的调度信息!

>>>>>: 下面补充线程调度策略和调度参数:
<1>.调度策略:

函数pthread_attr_setschedpolicy和pthread_attr_getschedpolicy分别用
来设置和得到线程的调度策略。

int pthread_attr_getschedpolicy(const pthread_attr_t *, int * policy)
int pthread_attr_setschedpolicy(pthread_attr_*, int policy)
参数:
attr 线程属性变量
policy 调度策略
若成功返回0,若失败返回-1。

所谓调度策略也就是我们之前在OS中所学过的那些调度算法:
SCHED_FIFO :先进先出
SCHED_RR :轮转法
SCHED_OTHER :其他方法

SCHED_OTHER是不支持优先级使用的,而SCHED_FIFO和SCHED_RR
支持优先级的使用,他们分别为1和99,数值越大优先级越高.

注意:
> 此处的SCHED_FIFO是允许被高优先级抢占的!
> 也就是有高优先级的必须先运行
> SCHED_RR是设置一个时间片
> 当有SCHED_FIFO或SCHED_RR策赂的线程在一个条件变量
上等持或等持加锁同一个互斥量时,它们将以优先级顺序被唤
醒。即,如果一个低优先级的SCHED_FIFO线程和一个高优先
织的SCHED_FIFO线程都在等待锁相同的互斥且,则当互斥量
被解锁时,高优先级线程将总是被首先解除阻塞。

<2>.调度参数:

函数pthread_attr_getschedparam 和pthread_attr_setschedparam分别
用来设置和得到线程的调度参数。



int pthread_attr_getschedparam(const pthread_attr_t *,struct
sched_param *);
int pthread_attr_setschedparam(pthread_attr_t *,const struct
sched_param *);
参数:
attr 线程变量属性
param sched_parm 结构体
若成功返回0,若失败返回-1。

/usr/include /bits/sched.h
struct sched_param
{
int sched_priority; //!> 参数的本质就是优先级
};
注意:大的权值对应高的优先级!
系统支持的最大和最小的优先级值可以用函数:
sched_get_priority_max和sched_get_priority_min得到!

#include <pthread.h>
int sched_get_priority_max( int policy );
int sched_get_priority_min( int policy );
参数:max_: 系统支持的优先级的最小值
min_ : 系统支持的优先级的最大值

使用:max_ = sched_get_priority_max( policy );
min_ = sched_get_priority_min( policy );
注意参数是policy调用策略,也就是说对于不同的策略的值是不
一样的!

附录:来自
yuanma.org/data/2006/08
policy = SCHED_OTHER
max_priority = 0
min_priority = 0

Show SCHED_FIFO of priority
max_priority = 99
min_priority = 1

Show SCHED_RR of priority
max_priority = 99
min_priority = 1

Show priority of current thread
priority = 0

3. 线程的作用域:

函数pthread_attr_setscope和pthread_attr_getscope分别
用来设置和得到线程的作用域。
#include <pthread.h>
int pthread_attr_getscope( const pthread_attr_t * attr, int * scope );
int pthread_attr_setscope( pthread_attr_t*, int scope );
参数:
attr 线程属性变量
scope 线程的作用域
若成功返回0,若失败返回-1。

作用域控制线程是否在进程内或在系统级上竞争资源,可能的值是
PTHREAD_SCOPE_PROCESS(进程内竞争资源)
PTHREAD_SCOPE_SYSTEM (系统级竞争资源)。

4. 线程堆栈的大小

函数pthread_attr_setstackaddr和pthread_attr_getstackaddr分别用来设置和得
到线程堆栈的位置。

int pthread_attr_getstacksize(const pthread_attr_t *,size_t * stacksize);
int pthread_attr_setstacksize(pthread_attr_t *attr ,size_t *stacksize);
参数:attr 线程属性变量
stacksize 堆栈大小
若成功返回0,若失败返回-1。

5. 线程堆栈的地址

#include <pthread.h>
int pthread_attr_getstackaddr(const pthread_attr_t *attr,void **stackaddf);
int pthread_attr_setstackaddr(pthread_attr_t *attr,void *stackaddr);
参数:attr 线程属性变量
stackaddr 堆栈地址
若成功返回0,若失败返回-1。

注意:pthread_attr_getstackaddr已经过期,现在使用的是:pthread_attr_getstack

6. 警戒缓冲区

函数pthread_attr_getguardsize和pthread_attr_setguardsize分别用来设置和得
到线程栈末尾的警戒缓冲区大小。

#include <pthread.h>
int pthread_attr_getguardsize(const pthread_attr_t *restrict attr,size_t *restrict
guardsize);
int pthread_attr_setguardsize(pthread_attr_t *attr ,size_t *guardsize);
若成功返回0,若失败返回-1。

值得注意:
线程属性guardsize控制着线程栈末尾之后以避免栈溢出的扩展内存
大小。这个属性默认设置为PAGESIZE个字节。可以把guardsize线
程属性设为0,从而不允许属性的这种特征行为发生:在这种情况
下不会提供警戒缓存区。同样地,如果对线程属性stackaddr作了
修改,系统就会认为我们会自己管理栈,并使警戒栈缓冲区机制无
效,等同于把guardsize线程属性设为0。

















参考文献

发布于 2020-01-02 10:09