线程同步
线程同步
如果变量时只读的,多个线程同时读取该变量不会有一致性问题,但是,当一个线程可以修改的变量,其他线程也可以读取或者修改的时候,我们就需要对这些线程进行同步,确保它们在访问变量的存储内容时不会访问到无效的值。
每个进程中访问临界资源的那段程序称为临界区,临界资源是一次仅允许一个进程使用的共享资源。线程同步的核心在于,通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。
互斥与同步
互斥与同步是我们在讨论线程安全时常常会碰到的概念:
-
互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
-
同步:主要是流程上的概念,是指在互斥的基础上(大多数情况
) ,通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
互斥锁、条件变量和信号量的区别: 互斥锁:互斥,一个线程占用了某个资源,那么其它的线程就无法访问,直到这个线程解锁,其它线程才可以访问。 条件变量:同步,一个线程完成了某一个动作就通过条件变量发送信号告诉别的线程,别的线程再进行某些动作。条件变量必须和互斥锁配合使用。 信号量:同步,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作。而且信号量有一个更加强大的功能,信号量可以用作为资源计数器,把信号量的值初始化为某个资源当前可用的数量,使用一个之后递减,归还一个之后递增。
另外还有以下几点需要注意:
1、信号量可以模拟条件变量,因为条件变量和互斥量配合使用,相当于信号量模拟条件变量和互斥量的组合。在生产者消费者线程池中,生产者生产数据后就会发送一个信号
同步原语
同步原语是一种软件机制,提供了两个或者多个并行进程或者线程在不同时刻执行一段相同的代码段的能力。例如下面的代码片段:
mutex_lock(&clocksource_mutex);
...
...
...
clocksource_enqueue(cs);
clocksource_enqueue_watchdog(cs);
clocksource_select();
...
...
...
mutex_unlock(&clocksource_mutex);
出自__clocksource_register_scale
函数,此函数添加给定的
这些函数展示了基于互斥锁
事实上,
spinlock; mutex; semaphores; seqlocks; atomic operations;
互斥量(Mutex
临界资源与临界区
所谓的临界资源,即一次只允许一个进程访问的资源,多个进程只能互斥访问的资源。临界资源的访问需要同步操作,比如信号量就是一种方便有效的进程同步机制。但信号量的方式要求每个访问临界资源的进程都具有
操作系统中管理的各种软件和硬件资源,均可用数据结构抽象地描述其资源特性,即用少量信息和对该资源所执行的操作来表征该资源,而忽略它们的内部结构和实现细节。利用共享数据结构抽象地表示系统中的共享资源。而把对该共享数据结构实施的操作定义为一组过程,如资源的请求和释放过程
管程就是代表共享资源的数据结构以及由对该共享数据结构实施操作的一组过程所组成的资源管理程序共同构成的一个操作系统的资源管理模块。管程被请求和释放临界资源的进程所调用。管程定义了一个数据结构和能为并发进程所执行
屏障(Barrier)
屏障是指用户可以协调多个线程并行工作的同步机制。屏障允许每个线程等待,直到所有的合作线程都到达某一点,然后从改点继续执行。
屏障对象的概念更广,它们允许任意数量的线程等待,直到所有的线程完成处理工作,而线程不需要退出。所有线程达到屏障后可以接着工作。
可以使用
#include <pthread. h>
//两个函数的返回值:若成功,返回0;否则,返回错误编码
int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned int count);
int pthread_barrier_destroy(pthread_barrier_t *barrier);
//返回值:若成功,返回0或者PTHREAD_BARRIER_SERIAL_THREAD;否则,返回错误编码
int pthread_barrier_wait(pthread_barrier_t *barrier);
初始化屏障时,可以使用
调用
在一个任务上合作的多个线程之间如何用屏障进行同步。
/*
程序功能:
创建一个动态数组,给数组赋初值,创建NUM_THREAD个线程,为长度为SIZE的数组初始化,通过(线程屏障同步)
当所有线程处理完后则打印整个数组的值。通过时间计数来比较所花费的时间。
当采用8个线程时花费sort took 0.0958 seconds
当采用四个线程时
thread -1249961072 done job.
thread -1260450928 done job.
thread -1239471216 done job.
thread -1270944880 done job.
sort took 0.1104 seconds
当采用一个线程时
thread -1239983216 done job.
sort took 0.2567 seconds
*/
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#define SIZE 8000000L //数组长度
#define NUM_THREAD 8 //线程个数
#define SIZE_PER (SIZE/NUM_THREAD)//每个线程要处理的数组长度
pthread_barrier_t barrier;//定义屏障
int *a;
/*每个线程的线程处理函数*/
void * thr_fun(void *arg)
{
long n = (long)arg;
long i;
for(i=n;i<n+SIZE_PER;i++) {
a[i] = i;
}
printf("thread %d done job.\n",pthread_self());
pthread_barrier_wait(&barrier); //阻塞等待直到主线程满足了屏障计数
return ((void *)1);
}
int main()
{
pthread_t tid;
struct timeval start,end;
long long startusec,endusec;
double elapsed;
int i;
a = (int *)malloc(SIZE*sizeof(int)); //动态分配数组
pthread_barrier_init(&barrier,NULL,NUM_THREAD+1);//初始化线程屏障计数为子线程个数加上主线程
gettimeofday(&start,NULL);//获得起始时间
for(i=0;i<NUM_THREAD;i++)
{
pthread_create(&tid,NULL,thr_fun,(void *)(i*SIZE_PER));//创建子线程
}
pthread_barrier_wait(&barrier);//等待所有子线程处理完成
gettimeofday(&end,NULL);//获得结束时间
for(i=0;i<SIZE;i++)//打印数组内容
//printf("%d ",a[i]);
startusec = start.tv_sec * 1000000 + start.tv_usec;
endusec = end.tv_sec * 1000000 + end.tv_usec;
elapsed = (double)(endusec-startusec)/1000000.0;//计算处理所花费的时间
printf("sort took %.4f seconds\n",elapsed);
return 0;
}