文章目录
1 前言
在前面文章描述的互斥机制中,互斥锁、读写锁、自旋锁,都是用于单个线程的互斥作用,一个线程申请获得资源(锁)后,线程开始执行。在实际应用中,存在“多个线程等待资源”的同步场景,即是所有线程获得资源后就绪,并发执行。屏障机制就是用于多线程并行(同步)执行的场景。
2 屏障
屏障( Barrier)是协调多个线程同步执行而使得某一线程进入等待状态的一种同步机制。假设A、B、C三个线程,使用了屏障机制,可能A先就绪,由于B、C未就绪,A此时处于等待状态,只有三个线程都就绪或者达到用户指定的的状态,三个线程并发执行。这一点,与条件变量中的“广播信号”类似,多个线程等待条件变量信号,某一线程广播条件变量信号后,所有等待线程被唤醒执行。
2.1 屏障特点
- 线程同步
- 协调多个线程同步执行
2.2 屏障适用场景
屏障与互斥锁、读写锁、自旋锁不同,不是用来保护共享资源的,而是与条件变量的功能类似,用于线程同步,并且是用于同步多个线程。
- 多个线程同步执行
2.3 屏障使用原则
- None
3 屏障使用
屏障使用的基本步骤为:
【1】创建屏障实例
【2】初始化屏障
【3】等待屏障资源
【5】销毁屏障
3.1 创建屏障
posix线程屏障以pthread_barrier_t
数据结构表示。互斥锁实例可以用静态和动态创建。
pthread_barrier_t barrier;
3.2 初始化屏障
屏障初始化只支持使用pthread_barrier_init
函数进行动态初始化 。
int pthread_barrier_init(pthread_barrier_t *barrier,
const pthread_barrierattr_t *attr,
unsigned count);
barrier
,屏障实例地址,不能为NULLattr
,屏障属性地址,传入NULL表示使用默认属性; 非默认属性,必须指定为进程作用域PTHREAD_PROCESS_PRIVATE
count
,等待线程个数- 返回,成功返回0,参数无效返回 EINVAL
3.3 等待屏障资源
int pthread_barrier_wait(pthread_barrier_t *barrier);
-
barrier
,屏障实例地址,不能为NULL -
返回,成功返回值有两个,参数无效返回 EINVAL
[1 ]最后一个等待线程调用返回
PTHREAD_BARRIER_SERIAL_THREAD
(-1),可以用此标识处理特殊任务,如区别哪个线程最后执行[2] 其他线程调用返回0
pthread_barrier_wait
函数用于等待线程就绪,在线程处理任务中调用,待所有线程就绪后,才开始执行。
void thread_entry(void*)
{
pthread_barrier_wait(&barrier);/* 等待就绪 */
/* todo 线程处理任务*/
}
3.4 屏障销毁
int pthread_barrier_destroy(pthread_barrier_t *barrier);
barrier
,屏障实例地址,不能为NULL- 返回,成功返回0,参数无效返回 EINVAL
pthread_barrier_destroy
用于销毁一个已经使用动态初始化的屏障。销毁后的屏障处于未初始化状态,屏障的属性和控制块参数处于不可用状态。使用销毁函数需要注意几点:
- 已销毁的屏障,可以使用
pthread_barrier_init
重新初始化使用 - 不能重复销毁已销毁的屏障
- 没有线程持有屏障时,且该屏障没有阻塞任何线程,才能销毁
3. 5 写个例子
代码实现功能:
- 创建三个线程
- 三个线程分别执行自加计数,并输出到终端
- 期望结果,三个线程同步执行
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include "pthread.h"
#define USE_BARRIER 1 /* 是否使用屏障,1使用,0不使用 */
/* 屏障 */
#if USE_BARRIER
static pthread_barrier_t barrier;
#endif
void *thread0_entry(void *data)
{
int temp =0;
uint32_t count = 0;
printf("thread0 startup\r\n");
#if USE_BARRIER
temp = pthread_barrier_wait(&barrier);
printf("thread0 barrier return[%d]\r\n", temp);
#endif
for (;;)
{
printf("thread0 count [%d]\r\n", count++);
sleep(1); /* 主动让出cpu */
}
}
void *thread1_entry(void *data)
{
int temp =0;
uint32_t count = 0;
sleep(1); /* 让线程0先执行 */
printf("thread1 startup\r\n");
#if USE_BARRIER
temp = pthread_barrier_wait(&barrier);
printf("thread1 barrier return[%d]\r\n", temp);
#endif
for (;;)
{
printf("thread1 count [%d]\r\n", count++);
sleep(1); /* 主动让出cpu */
}
}
void *thread2_entry(void *data)
{
int temp =0;
uint32_t count = 0;
sleep(2); /* 让线程1先执行 */
printf("thread2 startup\r\n");
#if USE_BARRIER
temp = pthread_barrier_wait(&barrier);
printf("thread2 barrier return[%d]\r\n", temp);
#endif
for (;;)
{
printf("thread2 count [%d]\r\n", count++);
sleep(1); /* 主动让出cpu */
}
}
int main(int argc, char **argv)
{
pthread_t thread0,thread1,thread2;
void *retval;
#if USE_BARRIER
pthread_barrier_init(&barrier,NULL,3);
#endif
pthread_create(&thread0, NULL, thread0_entry, NULL);
pthread_create(&thread1, NULL, thread1_entry, NULL);
pthread_create(&thread2, NULL, thread2_entry, NULL);
pthread_join(thread0, &retval);
pthread_join(thread1, &retval);
pthread_join(thread2, &retval);
return 0;
}
不加屏障的结果
不使用屏障,线程0线执行,接着是线程1执行,最后执行的是线程2。
acuity@ubuntu:/mnt/hgfs/LSW/STHB/pthreads/Barrier$ gcc barrier.c -o barrier -lpthread
acuity@ubuntu:/mnt/hgfs/LSW/STHB/pthreads/Barrier$ ./barrier
thread0 startup
thread0 count [0]
thread1 startup
thread1 count [0]
thread0 count [1]
thread2 startup
thread2 count [0]
使用屏障的结果
增加屏障等待后,即使线程0已就绪,但线程1和线程2因为sleep
主动挂起处于未就绪状态,线程0阻塞等待,直至线程1和线程2就绪,然后三个线程执行。
acuity@ubuntu:/mnt/hgfs/LSW/STHB/pthreads/Barrier$ gcc barrier.c -o barrier -lpthread
acuity@ubuntu:/mnt/hgfs/LSW/STHB/pthreads/Barrier$ ./barrier
thread0 startup
thread1 startup
thread2 startup
thread2 barrier return[-1]
thread2 count [0]
thread1 barrier return[0]
thread1 count [0]
thread0 barrier return[0]
thread0 count [0]
thread1 count [1]
thread2 count [1]
thread0 count [1]
代码中,使用了sleep
函数模拟线程耗时过程,实际场景可能不会这样使用,这里只是模拟场景测试。
4 屏障属性
使用默认的屏障锁属性可以满足绝大部分的应用场景,目前POSIX屏障机制属性暂时只支持“作用域”,且只支持“进程内作用域”PTHREAD_PROCESS_PRIVATE
。可以先熟悉了解屏障属性设置,基本步骤为:
【1】创建屏障属性实例
【2】初始化属性实例
【3】设置属性
【4】销毁属性实例
4.1 创建屏障属性
posix线程屏障属性以pthread_barrierattr_t
数据结构表示。屏障属性实例可以用静态和动态创建。
pthread_barrierattr_t attr;
4.2 屏障属性初始化与销毁
int pthread_barrierattr_init(pthread_barrierattr_t *attr);
int pthread_barrierattr_destroy(pthread_barrierattr_t *attr);
attr
,屏障属性实例地址,不能为NULL- 成功返回0,参数无效返回 EINVAL
设置屏障属性时,首先创建一个属性pthread_barrierattr_t
实例,然后调用pthread_barrierattr_init
函数初始实例,接下来就是属性设置。初始化后的属性值就是默认屏障属性,等价于使用pthread_barrier_init
采用默认属性(attr
传入NULL)的初始化。
4.3 屏障作用域
屏障作用域表示屏障的作用范围,分为进程内(创建者)作用域PTHREAD_PROCESS_PRIVATE
和跨进程作用域PTHREAD_PROCESS_SHARED
。进程内作用域只能用于进程内线程同步,跨进程可以用于系统所有线程间同步。
目前屏障只支持进程内作用域PTHREAD_PROCESS_PRIVATE
。
作用域设置与获取函数:
int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr, int pshared);
int pthread_barrierattr_getpshared(const pthread_barrierattr_t *attr, int *pshared);
attr
屏障属性实例地址,不能为NULLpshared
,作用域类型,PTHREAD_PROCESS_PRIVATE
- 成功返回0,参数无效返回 EINVAL
5 总结
屏障是协调多个线程同步执行的一种同步机制,使用了屏障机制的线程,任一线程未就绪时,其他已就绪的线程都会阻塞,直至屏障机制中所有线程就绪,然后所有线程同步执行。