Linux同步机制(二) - 条件变量,信号量,文件锁,栅栏
1 条件变量
条件变量是一种同步机制,允许线程挂起,直到共享数据上的某些条件得到满足。
1.1 相关函数
#include <pthread.h>
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t*cond_attr);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
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_destroy(pthread_cond_t *cond);
1.2 说明
1. 条件变量是一种同步机制,允许线程挂起,直到共享数据上的某些条件得到满足。条件变量上的基本操作有:触发条件(当条件变为true时);等待条件,挂起线程直到其他线程触发条件。
2. 条件变量要和互斥量相联结,以避免出现条件竞争--一个线程预备等待一个条件变量,当它在真正进入等待之前,另一个线程恰好触发了该条件。
pthread_cond_init 使用 cond_attr指定的属性初始化条件变量 cond,当 cond_attr为
NULL 时,使用缺省的属性。LinuxThreads实现条件变量不支持属性,因此 cond_attr参数实际被忽略。
pthread_cond_t 类型的变量也可以用 PTHREAD_COND_INITIALIZER常量进行静态初始化。
pthread_cond_signal 使在条件变量上等待的线程中的一个线程重新开始。如果没有等待的线程,则什么也不做。如果有多个线程在等待该条件,只有一个能重启动,但不能指定哪一个。
pthread_cond_broadcast 重启动等待该条件变量的所有线程。如果没有等待的线程,则什么也不做。
pthread_cond_wait 自动解锁互斥量(如同执行了 pthread_unlock_mutex),并等待条件变量触发。这时线程挂起,不占用
CPU 时间,直到条件变量被触发。在调用 pthread_cond_wait
之前,应用程序必须加锁互斥量。pthread_cond_wait 函数返回前,自动重新对互斥量加锁(如同执行了 pthread_lock_mutex)。
互斥量的解锁和在条件变量上挂起都是自动进行的。因此,在条件变量被触发前,如果所有的线程都要对互斥量加锁,这种机制可保证在线程加锁互斥量和进入等待条件变量期间,条件变量不被触发。
pthread_cond_timedwait 和 pthread_cond_wait一样,自动解锁互斥量及等待条件变量,但它还限定了等待时间。如果在 abstime指定的时间内
cond 未触发,互斥量 mutex被重新加锁,且 pthread_cond_timedwait返回错误 ETIMEDOUT。abstime参数指定一个绝对时间,时间原点与
time和 gettimeofday相同:abstime = 0表示 1970年 1月 1日
00:00:00 GMT。
pthread_cond_destroy 销毁一个条件变量,释放它拥有的资源。进入 pthread_cond_destroy之前,必须没有在该条件变量上等待的线程。在 LinuxThreads的实现中,条件变量不联结资源,除检查有没有等待的线程外,pthread_cond_destroy实际上什么也不做。
3. 取消
pthread_cond_wait 和 pthread_cond_timedwait是取消点。如果一个线程在这些函数上挂起时被取消,线程立即继续执行,然后再次对 pthread_cond_wait和 pthread_cond_timedwait在
mutex参数加锁,最后执行取消。因此,当调用清除处理程序时,可确保,mutex是加锁的。
4. 异步信号安全(Async-signalSafety)
条件变量函数不是异步信号安全的,不应当在信号处理程序中进行调用。特别要注意,如果在信号处理程序中调用 pthread_cond_signal或 pthread_cond_boardcast函数,可能导致调用线程死锁。
5. 返回值
在执行成功时,所有条件变量函数都返回 0,错误时返回非零的错误代码。
6. 错误代码
pthread_cond_init, pthread_cond_signal, pthread_cond_broadcast,和 pthread_cond_wait从不返回错误代码。
pthread_cond_timedwait 函数出错时返回下列错误代码:
ETIMEDOUT abstime 指定的时间超时时,条件变量未触发
EINTR pthread_cond_timedwait被触发中断
pthread_cond_destroy 函数出错时返回下列错误代码:
EBUSY 某些线程正在等待该条件变量
7. 举例
设有两个共享的变量 x 和 y,通过互斥量 mut保护,当 x > y时,条件变量 cond被触发。
int x,y;
int x,y;
pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
等待直到 x > y
的执行流程:
pthread_mutex_lock(&mut);
while (x <= y) {
pthread_cond_wait(&cond, &mut);
}
/* 对 x、y进行操作 */
pthread_mutex_unlock(&mut);
对 x 和 y的修改可能导致 x > y,应当触发条件变量:
pthread_mutex_lock(&mut);
/* 修改 x、y */
if (x > y) pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mut);
如果能够确定最多只有一个等待线程需要被唤醒(例如,如果只有两个线程通过 x、y通信),则使用 pthread_cond_signal比
pthread_cond_broadcast 效率稍高一些。如果不能确定,应当用pthread_cond_broadcast。
要等待在 5 秒内 x > y,这样处理:
struct timeval now;
struct timespec timeout;
int retcode;
pthread_mutex_lock(&mut);
gettimeofday(&now);
timeout.tv_sec = now.tv_sec + 5;
timeout.tv_nsec = now.tv_usec * 1000;
retcode = 0;
while (x <= y && retcode != ETIMEDOUT) {
retcode =pthread_cond_timedwait(&cond, &mut, &timeout);
}
if (retcode == ETIMEDOUT) {
/* 发生超时 */
} else {
/* 操作 x 和 y */
}
pthread_mutex_unlock(&mut);
============================
2 信号量
Semaphore,即信号量(sem_init),用来保护多重资源的访问,它可以设置一个大于0的值,如N,任何访问者在该信号量大于1的情况下均可以获得资源的访问权,并将相应的信号量减1。一般在为了线程在某一定程度上的顺序执行才使用信号量,即线程A等待线程B执行完某些操作以后,才能继续往下执行,可以理解为,组装厂A需要等待(sem_wait)元件厂B交付元件以后(sem_post)才能继续生产。当信号量不再使用时,销毁它(sem_destroy)。
2.1 相关函数
sem_init(sem_t *sem, int pshared, unsignedint value):初始化一个信号量
sem_wait(sem_t *sem):一直等待信号量,直到信号量大于0
int sem_trywait(sem_t *sem):等待信号量,没有成功立即返回
sem_timedwait(sem_t *sem, const structtimespec *abs_timeout):设定超时等待。
sem_post(sem_t *sem):信号量加1
sem_destory(sem_t *sem):释放信号量。
2.2 代码讲解:信号量的使用
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #include <semaphore.h> sem_t binSem; void* helloWorld(void* arg) { while(1) { // Wait semaphore sem_wait(&binSem); printf("Hello World\n"); } } int main(int argc, char** argv)
{ // Result for System call int res = 0; // Initialize semaphore sem_init(&binSem, 0, 0); // Create thread pthread_t thdHelloWorld; pthread_create(&thdHelloWorld, NULL, helloWorld, NULL); while(1) { // Post semaphore sem_post(&binSem); printf("In main, sleep several seconds.\n"); sleep(1); } // Wait for thread synchronization void *threadResult; pthread_join(thdHelloWorld, &threadResult); return 0; }
结果说明:
[root@rocket lock-free]# ./pthread_sem
In main, sleep several seconds.
Hello World
In main, sleep several seconds.
Hello World
In main, sleep several seconds.
Hello World
In main, sleep several seconds.
Hello World
In main, sleep several seconds.
Hello World
3 文件锁
3.1 文件锁介绍
linux下可以使用flock函数对文件进行加锁解锁等操作。简单介绍下flock()函数:
表头文件#include <sys/file.h>
定义函数 int flock(int fd,int operation);
函数说明 flock()会依参数operation所指定的方式对参数fd所指的文件做各种锁定或解除锁定的动作。此函数只能锁定整个文件,无法锁定文件的某一区域。
参数 operation有下列四种情况:
LOCK_SH 建立共享锁定。多个进程可同时对同一个文件作共享锁定。
LOCK_EX 建立互斥锁定。一个文件同时只有一个互斥锁定。
LOCK_UN 解除文件锁定状态。
LOCK_NB 无法建立锁定时,此操作可不被阻断,马上返回进程。通常与LOCK_SH或LOCK_EX做OR(|)组合。
单一文件无法同时建立共享锁定和互斥锁定,而当使用dup()或fork()时文件描述词不会继承此种锁定。
返回值返回0表示成功,若有错误则返回-1,错误代码存于errno。
3.2 代码讲解:文件锁的使用
file_lock_a.cpp
#include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <sys/file.h> int main(int argc, char** argv) { FILE *fp = NULL; int i = 20; if ((fp = fopen("./file_lock.test", "r+b")) == NULL)//打开文件 { printf("file open error!\n"); exit(0); } if (flock(fp->_fileno, LOCK_EX) != 0) //给该文件加锁 printf("file lock by others\n"); while(1) //进入循环,加锁时间为20秒,打印倒计时 { printf("in a, %d\n", i--); sleep(1); if (i == 0) break; } fclose(fp); //20秒后退出,关闭文件 flock(fp->_fileno, LOCK_UN); //文件解锁 return 0; }
file_lock_b.cpp
#include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <sys/file.h> int main(int argc, char** argv) { FILE *fp = NULL; int i = 20; if ((fp = fopen("./file_lock.test", "r+b")) == NULL) { printf("file open error!\n"); exit(0); } flock(fp->_fileno, LOCK_EX); while(1) //进入循环,加锁时间为20秒,打印倒计时 { printf("in b, %d\n", i--); sleep(1); if(i == 0) break; } fclose(fp); //20秒后退出,关闭文件 flock(fp->_fileno, LOCK_UN); //文件解锁 return 0; }
结果说明:
先创建文件touch file_lock.test
先运行file_lock_a,20秒以内在另一个终端运行file_lock_b,可以看到file_lock_a打印结束了file_lock_b才开始打印的现象。
4 栅栏
4.1 相关函数
pthread_barrier 系列函数在<pthread.h>中定义,用于多线程的同步,它包含三个函数:
#include <pthread.h>
int pthread_barrier_init(pthread_barrier_t*restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned count);
int pthread_barrier_wait(pthread_barrier_t*barrier);
intpthread_barrier_destroy(pthread_barrier_t *barrier);
参数解释:
pthread_barrier_t,是一个计数锁,对该锁的操作都包含在三个函数内部,我们不用关心也无法直接操作。只需要实例化一个对象丢给它就好。
pthread_barrierattr_t,锁的属性设置,设为NULL让函数使用默认属性即可。
count,你要指定的等待个数。
4.2 功能说明
那么pthread_barrier_*是用来做什么的?这三个函数又怎么配合使用呢?
pthread_barrier_*其实只做且只能做一件事,就是充当栏杆(barrier意为栏杆)。形象的说就是把先后到达的多个线程挡在同一栏杆前,直到所有线程到齐,然后撤下栏杆同时放行。
1)init函数负责指定要等待的线程个数;
2)wait()函数由每个线程主动调用,它告诉栏杆“我到起跑线前了”。wait()执行末尾栏杆会检查是否所有人都到栏杆前了,如果是,栏杆就消失所有线程继续执行下一句代码;如果不是,则所有已到wait()的线程停在该函数不动,剩下没执行到wait()的线程继续执行;
3)destroy函数释放init申请的资源。
4.3 使用场景举例
这种“栏杆”机制最大的特点就是最后一个执行wait的动作最为重要,就像赛跑时的起跑枪一样,它来之前所有人都必须等着。所以实际使用中,pthread_barrier_*常常用来让所有线程等待“起跑枪”响起后再一起行动。比如我们可以用pthread_create()生成100个线程,每个子线程在被create出的瞬间就会自顾自的立刻进入回调函数运行。但我们可能不希望它们这样做,因为这时主进程还没准备好,和它们一起配合的其它线程还没准备好,我们希望它们在回调函数中申请完线程空间、初始化后停下来,一起等待主进程释放一个“开始”信号,然后所有线程再开始执行业务逻辑代码。
解决方案:
为了解决上述场景问题,我们可以在init时指定n+1个等待,其中n是线程数。而在每个线程执行函数的首部调用wait()。这样100个pthread_create()结束后所有线程都停下来等待最后一个wait()函数被调用。这个wait()由主进程在它觉得合适的时候调用就好。最后这个wait()就是鸣响的起跑枪。
4.4 代码讲解:barrier的使用
#include <stdio.h> #include <pthread.h> #include <unistd.h> #include <stdlib.h> pthread_barrier_t barrier; int thread_num = 10; void* doSomething(void* arg) { printf("beforewait %d\n", pthread_self()); pthread_barrier_wait(&barrier);//所有线程都被阻塞在这里 printf("I'min pthread %d\n", pthread_self()); returnNULL; } int activate() { //等一切都安排好了调用该函数。起跑枪“砰!” pthread_barrier_wait(&barrier); return0; } int main(int argc, char** argv) { pthread_attr_tattr; pthread_attr_init(&attr); pthread_t*thread = (pthread_t*)malloc(thread_num * sizeof(pthread_t)); pthread_barrier_init(&barrier,NULL, thread_num + 1); for(inti = 0; i < thread_num; i++) { pthread_create(thread+i,&attr, doSomething, NULL); } activate(); for(int i = 0; i < thread_num; i++) { pthread_join(*(thread+i),NULL); } pthread_attr_destroy(&attr); return0; }
代码说明:
无栅栏时候的运行结果(注释掉doSomething和activate中的pthread_barrier_wait)
before wait 169158400
I'm in pthread 169158400
before wait 158668544
I'm in pthread 158668544
before wait 190138112
I'm in pthread 190138112
before wait 179648256
I'm in pthread 179648256
before wait 148178688
I'm in pthread 148178688
before wait 137688832
I'm in pthread 137688832
before wait 127198976
I'm in pthread 127198976
before wait 106219264
I'm in pthread 106219264
before wait 116709120
I'm in pthread 116709120
before wait 95729408
I'm in pthread 95729408
有栅栏时候的运行结果
[root@rocket lock-free]# ./pthread_barrier
before wait 2137720576
before wait 2106251008
before wait 2127230720
before wait 2085271296
before wait 2095761152
before wait 2053801728
before wait 2074781440
before wait 2116740864
before wait 2043311872
before wait 2064291584
I'm in pthread 2127230720
I'm in pthread 2137720576
I'm in pthread 2095761152
I'm in pthread 2106251008
I'm in pthread 2085271296
I'm in pthread 2064291584
I'm in pthread 2053801728
I'm in pthread 2074781440
I'm in pthread 2043311872
I'm in pthread 2116740864
版权声明:本文为博主原创文章,未经博主允许不得转载。
Linux同步机制(二) - 条件变量,信号量,文件锁,栅栏的更多相关文章
- Linux互斥锁、条件变量和信号量
Linux互斥锁.条件变量和信号量 来自http://kongweile.iteye.com/blog/1155490 http://www.cnblogs.com/qingxia/archive/ ...
- 【av68676164(p31-p32)】Windows和Linux同步机制
4.6.1 Windows同步机制 临界区(CRITICAL_SECTION) 在进程内使用,保证仅一个线程可以申请到该对象 临界区内是临界资源的访问 相关的API函数 初始化临界区 WINBASEA ...
- C++11 多线程同步 互斥锁 条件变量
在多线程程序中,线程同步(多个线程访问一个资源保证顺序)是一个非常重要的问题,Linux下常见的线程同步的方法有下面几种: 互斥锁 条件变量 信号量 这篇博客只介绍互斥量和条件变量的使用. 互斥锁和条 ...
- linux 互斥锁和条件变量
为什么有条件变量? 请参看一个线程等待某种事件发生 注意:本文是linux c版本的条件变量和互斥锁(mutex),不是C++的. mutex : mutual exclusion(相互排斥) 1,互 ...
- [转]Posix-- 互斥锁 条件变量 信号量
这是一个关于Posix线程编程的专栏.作者在阐明概念的基础上,将向您详细讲述Posix线程库API.本文是第三篇将向您讲述线程同步. 互斥锁 尽管在Posix Thread中同样可以使用IPC的信号量 ...
- linux同步机制
很早之前就接触过同步这个概念了,但是一直都很模糊,没有深入地学习了解过,近期有时间了,就花时间研习了一下<linux内核标准教程>和<深入linux设备驱动程序内核机制>这两本 ...
- Linux同步机制(一) - 线程锁
1 互斥锁 在线程实际运行过程中,我们经常需要多个线程保持同步. 这时可以用互斥锁来完成任务.互斥锁的使用过程中,主要有 pthread_mutex_init pthread_mutex_destor ...
- linux 同步机制之complete【转】
转自: http://blog.csdn.net/wealoong/article/details/8490654 在Linux内核中,completion是一种简单的同步机制,标志"thi ...
- Linux多线程编程的条件变量
在stackoverflow上看到一关于多线程条件变量的问题,题主问道:什么时候会用到条件变量,mutex还不够吗?有个叫slowjelj的人做了很好的回答,我再看这个哥们其他话题的一些回答,感觉水平 ...
随机推荐
- ios 缓存相关信息收集
链接:http://www.cnblogs.com/pengyingh/category/353093.html 使用NSURLCache让本地数据来代替远程UIWebView请求 摘要: 原文作者: ...
- AVFoundation的使用
AVFoundation的使用 2013-05-03 14:50:21| 分类: iphone_dev_note|举报|字号 订阅 相机相关应用一般会用到AVFoundation. ...
- 功率单位mW 和 dBm 的换算
无线电发射机输出的射频信号,通过馈线(电缆)输送到天线,由天线以电磁波形式辐射出去.电磁波到达接收地点后,由天线接收下来(仅仅接收很小很小一部分功率),并通过馈线送到无线电接收机.因此在无线网络的工程 ...
- Coder-Strike 2014 - Finals (online edition, Div. 2) C题
C. Online Meeting time limit per test 1 second memory limit per test 256 megabytes input standard in ...
- B股
B股的正式名称是人民币特种股票.它是以人民币标明面值,以外币认购和买卖,在中国境内(上海.深圳)证券交易所上市交易的外资股.B股公司的注册地和上市地都在境内.
- LoaderManager使用详解(一)---没有Loader之前的世界
来源: http://www.androiddesignpatterns.com/2012/07/loaders-and-loadermanager-background.html 感谢作者Alex ...
- .htaccess文件的作用(访问控制)
在线工具: http://www.htaccesseditor.com/sc.shtml 说到.htaccess文件,我想对于wordpress新手或者老手都应该不是很熟悉,也没有多少这方面的概念吧, ...
- asp.net中Literal与label的区别
Literal 控件表示用于向页面添加内容的几个选项之一.对于静态内容,无需使用容器,可以将标记作为 HTML 直接添加到页面中.但是,如果要动态添加内容,则必须将内容添加到容器中.典型的容器有 La ...
- 多线程系列 线程池ThreadPool
上一篇文章我们总结了多线程最基础的知识点Thread,我们知道了如何开启一个新的异步线程去做一些事情.可是当我们要开启很多线程的时候,如果仍然使用Thread我们需要去管理每一个线程的启动,挂起和终止 ...
- Spring MVC 教程,快速入门,深入分析(转)
原文地址:http://elf8848.iteye.com/blog/875830/