Linux c编程:同步属性
就像线程具有属性一样,线程的同步对象(如互斥量、读写锁、条件变量、自旋锁和屏障)也有属性
1、互斥量属性
用pthread_mutexattr_init初始化pthread_mutexattr_t结构,用pthread_mutexattr_destroy来对该结构进行回收。
#include <pthread.h>
int pthread_mutexattr_init( pthread_mutexattr_t *attr );
int pthread_mutexattr_destroy( pthread_mutexattr_t *attr );
返回值:若成功则返回0,否则返回错误编号
pthread_mutexattr_init函数用默认的互斥量属性初始化pthread_mutexattr_t结构。值得注意的两个属性是进程共享属性和类型属性。POSIX.1中,进程共享属性是可选的,可以通过检查系统中是否定义了_POSIX_THREAD_PROCESS_SHARED符号来判断这个平台是否支持进程共享这个属性,也可以在运行时把_SC_THREAD_PROCESS_SHARED参数传给sysconf函数进行检查。虽然这个选项并不是遵循POSIX标准的操作系统必须提供的,但是Single UNIX Specification要求遵循XSI标准的操作系统支持这个选项。
在进程中,多个线程可以访问同一个同步对象,这是默认的行为。在这种情况下,进程共享互斥量属性需要设置为PTHREAD_PROCESS_PRIVATE。
存在这样一种机制:允许相互独立的多个进程把同一个内存区域映射到它们各自独立的地址空间中。就像多个线程访问共享数据一样,多个进程访问共享数据通常也需要同步。如果进程共享互斥量属性设置为PTHREAD_PROCESS_SHARED,从多个进程共享的内存区域中分配的互斥量就可以用于这些进程的同步。
可以使用pthread_mutexattr_getpshared函数查询pthread_mutexattr_t结构,得到它的进程共享属性;可以用pthread_mutexattr_setpshared函数修改进程共享属性。
#include <pthread.h>
int pthread_mutexattr_getpshared( const pthread_mutexattr_t *restrict attr, int *restrict pshared );
int pthread_mutexattr_setpshared( pthread_mutexattr_t *attr, int pshared );
返回值:若成功则返回0,否则返回错误编号
进程共享互斥量属性设为PTHREAD_PROCESS_PRIVATE时,允许pthread线程库提供更加有效的互斥量实现,这在多线程应用程序中是默认的情况。在多个进程共享多个互斥量的情况下,pthread线程库可以限制开销较大的互斥量实现。
类型互斥量属性控制着互斥量的特性。POSIX.1定义了四种类型。PTHREAD_MUTEX_NORMAL类型是标准的互斥量类型,并不做任何特殊的错误检查或死锁检测。PTHREAD_MUTEX_ERRORCHECK互斥量类型提供错误检查。
PTHREAD_MUTEX_RECURSIVE互斥量类型允许同一线程在互斥量解锁之前对该互斥量进行多次加锁。用一个递归互斥量维护锁的计数。在解锁的次数和加锁的次数不相同的情况下不会释放锁。所以如果对一个递归互斥量加锁两次,然后对它解锁一次,这个互斥量依然处于加锁状态,在对它再次解锁以前不能释放该锁。
最后,PTHREAD_MUTEX_DEFAULT类型可以用于请求默认语义。操作系统在实现它的时候可以把这种类型自由地映射到其他类型。例如,在Linux中,这种类型映射为普通的互斥量类型。
四种类型的行为如下表所示。“不占用时解锁”这一栏指的是一个线程对被另一个线程加锁的互斥量进行解锁的情况;“在已解锁时解锁”这一栏指的是当一个线程对已经解锁的互斥量进行解锁时将会发生的情况,这通常是编码错误所致。
可以用pthread_mutexattr_gettype函数得到互斥量类型属性,用pthread_mutexattr_settype函数修改互斥量类型属性。
#include <pthread.h>
int pthread_mutexattr_gettype( const pthread_mutexattr_t * restrict attr, int *restrict type );
int pthread_mutexattr_settype( pthread_mutexattr_t *attr, int type );
两者的返回值都是:若成功则返回0,否则返回错误编号
互斥量可用于保护与条件变量关联的条件。在阻塞线程之前,pthread_cond_wait和pthread_cond_timedwait函数释放与条件相关的互斥量,这就允许其他线程获取互斥量、改变条件、释放互斥量并向条件变量发送信号。既然改变条件时必须占有互斥量,所以使用递归互斥量并不是好的办法。如果递归互斥量被多次加锁,然后用在调用pthread_cond_wait函数中,那么条件永远都不会得到满足,因为pthread_cond_wait所作的解锁操作并不能释放互斥量。
如果需要把现有的单线程接口放到多线程环境中,递归互斥量是非常有用的,但由于程序兼容性的限制,不能对函数接口进行修改。然而由于递归锁的使用需要一定技巧,它只应在没有其他可行方案的情况下使用。
使用递归锁的情况:
main()
{
func1(x);
......
func2(x);
}
func1()
{
pthread_mutex_lock(x->lock);
......
func2(x);
......
pthread_mutex_unlock(x->lock);
}
func2()
{
pthread_mutex_lock(x->lock);
......
pthread_mutex_unlock(x->lock);
}
上面的代码解释了递归锁看似解决并发问题的情况。假设func1和func2是函数库中现有的函数,其接口不能改变,因为存在调用这两个接口的应用程序,而且应用程序不能改动。
为了保持接口跟原来相同,可以把互斥量嵌入到数据结构中,把这个数据结构的地址(x)作为参数传入。这种方案只有在为该数据结构提供了分配函数时才可行,所以应用并不知道数据结构的大小(假设在其中增加互斥量后必须扩大该数据结构的大小)。
如果在最初定义数据结构时,预留了足够的可填充字段,允许把一些填充字段替换成互斥量,那么这种方法也是可行的。不过,大多数程序员并不善于预测未来,所以这不是普遍可行的经验。
如果func1和func2函数都必须操作这个结构,而且可能会有多个线程同时访问该数据结构,那么func1和func2必须在操作数据以前对互斥量加锁。当func1必须调用func2时,如果互斥量不是递归类型,那么就会出现死锁。如果能在调用func2之前释放互斥量,在func2返回后重新获取互斥量,那么就可以避免使用递归互斥量,但这也给其他的线程提供了机会,其他的线程可能在func1执行期间得到互斥量的控制权,修改这个数据结构。
避免使用递归锁的情况
main()
{
func1(x);
......
func2(x);
}
func1()
{
pthread_mutex_lock(x->lock);
......
func2_locked(x);
......
pthread_mutex_unlock(x->lock);
}
func2()
{
pthread_mutex_lock(x->lock);
func2_locked(x);
pthread_mutex_unlock(x->lock);
}
上面的代码显示了这种情况下使用递归互斥量的另一种替代方法。通过提供func2函数的私有版本(称之为func2_locked函数),可以保持func1和func2函数接口不变,并且避免使用递归互斥量。要调用func2_locked函数,必须占有嵌入到数据结构中的互斥量,这个数据结构的地址是作为参数传入的。func2_locked的函数体包含func2的副本(原来的、没有加入互斥量的func2),func2现在只是用以获取互斥量,调用func2_locked,最后释放互斥量。
如果并不一定要保持库函数接口不变,就可以在每个函数中另外再加一个参数,以表明这个结构是否被调用者锁定。但是,如果可能的话,保持接口不变通常是更好的选择,这样可以避免实现过程中人为加入的东西对原有接口产生不良影响。
提供函数的加锁版本和不加锁版本,这样的策略在简单的情况下通常是可行的。在比较复杂的情况下,例如库需要调用库以外的函数,而且可能会再次回调库中的函数时,就需要依赖递归锁。
下面解释了有必要使用递归互斥量的另一种情况。这里,有一个“超时”(timeout)函数,它允许另一个函数可以安排在未来的某个时间运行。假设线程并不是很昂贵的资源,可以为每个未决的超时函数创建一个线程。线程在时间未到时将一直等待,时间到了以后就调用请求的函数。
#include "apue.h"
#include <pthread.h>
#include <time.h>
#include <sys/time.h>
extern int makethread(void *(*)(void *), void *);
struct to_info{
void (*to_fn)(void *); /* function */
void *to_arg; /* argument */
struct timespec to_wait; /* time to wait */
};
#define SECTONSEC 1000000000 /* seconds to nanoseconds */
#define USECTONSEC 1000 /* microseconds to nanoseconds */
void *
timeout_helper(void *arg)
{
struct to_info *tip;
tip = (struct to_info *)arg;
nanosleep(&tip->to_wait, NULL);
(*tip->to_fn)(tip->to_arg);
return(0);
}
void
timeout(const struct timespec *when, void (*func)(void *), void *arg)
{
struct timespec now;
struct timeval tv;
struct to_info *tip;
int err;
gettimeofday(&tv, NULL);
now.tv_sec = tv.tv_sec;
now.tv_nsec = tv.tv_usec * USECTONSEC;
if((when->tv_sec > now.tv_sec) ||
(when->tv_sec == now.tv_sec && when->tv_nsec > now.tv_nsec))
{
tip = malloc(sizeof(struct to_info));
if(tip != NULL)
{
tip->to_fn = func;
tip->to_arg = arg;
tip->to_wait.tv_sec = when->tv_sec - now.tv_sec;
if(when->tv_nsec >= now.tv_nsec)
{
tip->to_wait.tv_nsec = when->tv_nsec - now.tv_nsec;
}
else
{
tip->to_wait.tv_sec--;
tip->to_wait.tv_nsec = SECTONSEC - now.tv_nsec + when->tv_nsec;
}
err = makethread(timeout_helper, (void *)tip);
if(err == 0)
return;
}
}
/*
* We get here if (a) when <= now, or (b) malloc fails, or
* (c) we can't make a thread, so we just call the function now.
*/
(*func)(arg);
}
pthread_mutexattr_t attr;
pthread_mutex_t mutex;
void
retry(void *arg)
{
pthread_mutex_lock(&mutex);
/* perform retry steps ... */
pthread_mutex_unlock(&mutex);
}
int
main(void)
{
int err, condition, arg;
struct timespec when;
if((err == pthread_mutexattr_init(&attr)) != 0)
err_exit(err, "pthread_mutexattr_init failed");
if((err == pthread_mutexattr_settype(&attr,
PTHREAD_MUTEX_RECURSIVE)) != 0)
err_exit(err, "can't set recursive type");
if((err == pthread_mutex_init(&mutex, &attr)) != 0)
err_exit(err, "can't create recursive mutex");
/* ... */
pthread_mutex_lock(&mutex);
/* ... */
if(condition)
{
/* calculate target time "when" */
timeout(&when, retry, (void *)arg);
}
/* ... */
pthread_mutex_unlock(&mutex);
/* ... */
exit(0);
}
如果不能创建线程,或者安排函数运行的时间已过,问题就出现了。在这种情况下,要从当前环境中调用之前请求运行的函数,因为函数要获取的锁和现在占有的锁是同一个,除非该锁是递归的,否则就会出现死锁。
这里使用程序清单12-1中的makethread函数以分离状态创建线程。希望函数在未来的某个时间运行,而且不希望一直等待线程结束。
可以调用sleep等待超时到达,但它提供的时间粒度是秒级的,如果希望等待的时间不是整数秒,需要用nanosleep(2)函数,它提供了类似的功能。
timeout的调用者需要占有互斥量来检查条件,并且把retry函数安排为原子操作。retry函数试图对同一个互斥量进行加锁,因此,除非互斥量是递归的,否则如果timeout函数直接调用retry就会导致死锁。
读写锁属性
读写锁与互斥量类似,也具有属性。用pthread_rwlockattr_init初始化pthread_rwlockattr_t结构,用pthread_rwlockattr_destroy回收结构。
#include <pthread.h>
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
两者的返回值都是:若成功则返回0,否则返回错误编号
读写锁支持的唯一属性是进程共享属性,该属性与互斥量的进程共享属性相同。就像互斥量的进程共享属性一样,用一对函数来读取和设置读写锁的进程共享属性。
#include <pthread.h>
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t * restrict attr, int *restrict pshared);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);
两者的返回值都是:若成功则返回0,否则返回错误编号
条件变量属性
条件变量也有属性。与互斥量和读写锁类似,有一对函数用于初始化和回收条件变量属性。
#include <pthread.h>
int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_destroy(pthread_condattr_t *attr);
两者的返回值都是:若成功则返回0,否则返回错误编号
与其他的同步原语一样,条件变量支持进程共享属性。
#include <pthread.h>
int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr, int *restrict pshared);
int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared);
两者的返回值都是:若成功则返回0,否则返回错误编号
Linux c编程:同步属性的更多相关文章
- Linux系统编程 —线程属性
在之前的章节中,我们在调用pthread_create函数创建线程时,第二个参数(即线程属性)都是设为NULL,即使用默认属性.一般情况下,使用默认属性已经可以解决我们开发过程中的大多数问题. 但是, ...
- LInux多线程编程----线程属性pthread_attr_t
1.每个POSIX线程有一个相连的属性对象来表示属性.线程属性对象的类型是pthread_attr_t,pthread_attr_t 在文件/usr/include/bits/pthreadtypes ...
- linux基础编程:IO模型:阻塞/非阻塞/IO复用 同步/异步 Select/Epoll/AIO(转载)
IO概念 Linux的内核将所有外部设备都可以看做一个文件来操作.那么我们对与外部设备的操作都可以看做对文件进行操作.我们对一个文件的读写,都通过调用内核提供的系统调用:内核给我们返回一个file ...
- linux C 多线程/线程池编程 同步实例
在多线程.线程池编程中经常会遇到同步的问题. 1.创建线程 函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, ...
- linux系统编程--线程同步
同步概念 所谓同步,即同时起步,协调一致.不同的对象,对“同步”的理解方式略有不同. 如,设备同步,是指在两个设备之间规定一个共同的时间参考: 数据库同步,是指让两个或多个数据库内容保持一致,或者按需 ...
- Linux 系统编程 学习:11-线程:线程同步
Linux 系统编程 学习:11-线程:线程同步 背景 上一讲 我们介绍了线程的属性 有关设置.这一讲我们来看线程之间是如何同步的. 额外安装有关的man手册: sudo apt-get instal ...
- Linux 系统编程 学习:10-线程:线程的属性
Linux 系统编程 学习:10-线程:线程的属性 背景 上一讲我们介绍了线程的创建,回收与销毁:简单地提到了线程属性.这一讲我们就来具体看看,线程的属性. 概述 #include <pthre ...
- storysnail的Linux串口编程笔记
storysnail的Linux串口编程笔记 作者 He YiJun – storysnail<at>gmail.com 团队 ls 版权 转载请保留本声明! 本文档包含的原创代码根据Ge ...
- Linux系统编程@多线程编程(二)
线程的操作 线程标识 线程的ID表示数据类型:pthread_t (内核中的实现是unsigned long/unsigned int/指向pthread结构的指针(不可移植)几种类型) 1.对两个线 ...
- Linux系统编程@进程通信(一)
进程间通信概述 需要进程通信的原因: 数据传输 资源共享 通知事件 进程控制 Linux进程间通信(IPC)发展由来 Unix进程间通信 基于System V进程间通信(System V:UNIX系统 ...
随机推荐
- eclipse maven项目导入Intellij问题处理
1.maven打包编译时后台一直输出警告信息 [WARNING] File encoding has not been set, using platform encoding GBK, i.e. b ...
- ionic - 运行起来
更新时间: 2018-8-1 (首次更新) 1.首先下载python(至于为什么安装,看截图) https://www.python.org/downloads/release/python-370/ ...
- iOS开发:解决UIScrollView不滚动的问题
照着书上的Demo(iOS 5.0的教程),在- (void)viewDidLoad里设置scrollView的contentsize,让它大于屏幕的高度,却发现在模拟器中没用,还是不能滚.经过 一翻 ...
- Nginx:访问第三方服务
参考资料<深入理解Nginx> Nginx可以当做一个强大的反向代理服务器,其反向代理模块是基于upstream方式实现的. upstream的使用方式 HTTP模块在处理任何一个请求时都 ...
- My sql 实用教程
http://wenku.baidu.com/link?url=uwWWeGTZU61MQSSArf2pYRd4jPd7k7gNsx75KxEUKO1MlMLAAFiIF-fus3CY4RLyyzbZ ...
- ylb:使用sql语句实现添加、删除约束
ylbtech-SQL Server:SQL Server-使用sql语句实现添加.删除约束 --主键约束(Primary Key constraint):要求主键列的数据唯一,并且不允许为空. -- ...
- OpenGL三角形的双面不同颜色的绘制
对于一个三角形,我要给它正反面不同的颜色.然后通过旋转,看出它的效果. 我只想到了2种方法,下面我来写一下这两种方法. 第一种方法,通过角度的判断重设glColor3f的参数(这种方法局限性很大,不推 ...
- Ubuntu下git使用
sudo apt-get install git //安装git git config --global user.name "github 用户名" git config --g ...
- Active Directory的基本概念
前言 本文是面对准备加入Active Directory编程的初学者的一份文章,主要是讲解Active Directory(活动目录)的一些概念和相关知识.这篇文章本来是不想写下来的,因为概念性内容的 ...
- CentOS7 安装 Node.js
1.首先安装node.js 的版本管理工具 NVM,执行以下命令: curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.32.1/ ...