线程同步方式比较
用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。
内核模式下的方法有:事件,信号量,互斥量。
临界区
保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么 在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操 作共享资源的目的。 仅能在同一进程内使用
互斥量 Mutex
互斥量跟临界区很相似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。互斥量比临界区复杂。因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。
信号量
信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源 ,这与操作系统中的PV操作相同。
事件(Event)
事件机制,则允许一个线程在处理完一个任务后,主动唤醒另外一个线程执行任务。
以上几种方法在win和linux下的API又有不同
Linux下实现同步的API:
互斥量 Mutex
- 初始化锁。在Linux下,线程的互斥量数据类型是pthread_mutex_t。在使用前,要对它进行初始化。
静态分配:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
动态分配:int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_attr_t *mutexattr); - 加锁。对共享资源的访问,要对互斥量进行加锁,如果互斥量已经上了锁,调用线程会阻塞,直到互斥量被解锁。
int pthread_mutex_lock(pthread_mutex *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex); - 解锁。在完成了对共享资源的访问后,要对互斥量进行解锁。
int pthread_mutex_unlock(pthread_mutex_t *mutex); - 销毁锁。锁在是使用完成后,需要进行销毁以释放资源。
int pthread_mutex_destroy(pthread_mutex *mutex);
信号量(sem)
如同进程一样,线程也可以通过信号量来实现通信,虽然是轻量级的。信号量函数的名字都以"sem_"打头。线程使用的基本信号量函数有四个。
- 信号量初始化。
int sem_init (sem_t *sem , int pshared, unsigned int value);
这是对由sem指定的信号量进行初始化,设置好它的共享选项(linux 只支持为0,即表示它是当前进程的局部信号量),然后给它一个初始值VALUE。 - 等待信号量。给信号量减1,然后等待直到信号量的值大于0。
int sem_wait(sem_t *sem); - 释放信号量。信号量值加1。并通知其他等待线程。
int sem_post(sem_t *sem); - 销毁信号量。我们用完信号量后都它进行清理。归还占有的一切资源。
int sem_destroy(sem_t *sem);
条件变量(cond)
互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。条件变量分为两部分: 条件和变量。条件本身是由互斥量保护的。线程在改变条件状态前先要锁住互斥量。条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。
- 初始化条件变量。
静态态初始化,pthread_cond_t cond = PTHREAD_COND_INITIALIER;
动态初始化,int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr); - 等待条件成立。释放锁,同时阻塞等待条件变量为真才行。timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait)
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime); - 激活条件变量。pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程)
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond); //解除所有线程的阻塞 - 清除条件变量。无线程等待,否则返回EBUSY
int pthread_cond_destroy(pthread_cond_t *cond);
屏障
barrier(屏障)与互斥量,读写锁,自旋锁不同,它不是用来保护临界区的。相反,它跟条件变量一样,是用来协同多线程一起工作!!!
条件变量是多线程间传递状态的改变来达到协同工作的效果。屏障是多线程各自做自己的工作,如果某一线程完成了工作,就等待在屏障那里,直到其他线程的工作都完成了,再一起做别的事。举个通俗的例子:
1.对于条件变量。在接力赛跑里,1号队员开始跑的时候,2,3,4号队员都站着不动,直到1号队员跑完一圈,把接力棒给2号队员,2号队员收到接力棒后就可以跑了,跑完再给3号队员。这里这个接力棒就相当于条件变量,条件满足后就可以由下一个队员(线程)跑。
2.对于屏障。在百米赛跑里,比赛没开始之前,每个运动员都在赛场上自由活动,有的热身,有的喝水,有的跟教练谈论。比赛快开始时,准备完毕的运动员就预备在起跑线上,如果有个运动员还没准备完(除去特殊情况),他们就一直等,直到运动员都在起跑线上,裁判喊口号后再开始跑。这里的起跑线就是屏障,做完准备工作的运动员都等在起跑线,直到其他运动员也把准备工作做完!
1.创建屏障
1 #include <pthread.h> 2 3 int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned count);
barrier:pthread_barrier_t结构体指针
attr:屏障属性结构体指针
count:屏障等待的线程数目,即要count个线程都到达屏障时,屏障才解除,线程就可以继续执行
2.等待
1 #include <pthread.h> 2 3 int pthread_barrier_wait(pthread_barrier_t *barrier);
函数的成功返回值有2个,第一个成功返回的线程会返回PTHREAD_BARRIER_SERIAL_THREAD,其他线程都返回0。可以用第一个成功返回的线程来做一些善后处理工作。
3.销毁屏障
1 #include <pthread.h> 2 3 int pthread_barrier_destroy(pthread_barrier_t *barrier);
例子:
/** * @file pthread_barrier.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> /* 屏障总数 */ #define PTHREAD_BARRIER_SIZE 4 /* 定义屏障 */ pthread_barrier_t barrier; void err_exit(const char *err_msg) { printf("error:%s\n", err_msg); exit(1); } void *thread_fun(void *arg) { int result; char *thr_name = (char *)arg; /* something work */ printf("线程%s工作完成...\n", thr_name); /* 等待屏障 */ result = pthread_barrier_wait(&barrier); if (result == PTHREAD_BARRIER_SERIAL_THREAD) printf("线程%s,wait后第一个返回\n", thr_name); else if (result == 0) printf("线程%s,wait后返回为0\n", thr_name); return NULL; } int main(void) { pthread_t tid_1, tid_2, tid_3; /* 初始化屏障 */ pthread_barrier_init(&barrier, NULL, PTHREAD_BARRIER_SIZE); if (pthread_create(&tid_1, NULL, thread_fun, "1") != 0) err_exit("create thread 1"); if (pthread_create(&tid_2, NULL, thread_fun, "2") != 0) err_exit("create thread 2"); if (pthread_create(&tid_3, NULL, thread_fun, "3") != 0) err_exit("create thread 3"); /* 主线程等待工作完成 */ pthread_barrier_wait(&barrier); printf("所有线程工作已完成...\n"); sleep(1); return 0; }
参考:http://www.cnblogs.com/yuuyuu/p/5152560.html
win下实现同步的API:
临界区(Critical Section)
EnterCriticalSection() 进入临界区
LeaveCriticalSection() 离开临界区
互斥量(Mutex)
CreateMutex() 创建一个互斥量
OpenMutex() 打开一个互斥量
ReleaseMutex() 释放互斥量
WaitForMultipleObjects() 等待互斥量对象
信号量(Semaphores)
CreateSemaphore() 创建一个信号量
OpenSemaphore() 打开一个信号量
ReleaseSemaphore() 释放信号量
WaitForSingleObject() 等待信号量
事件(Event)
CreateEvent() 创建一个事件
OpenEvent() 打开一个事件
SetEvent() 回置事件
WaitForSingleObject() 等待一个事件
WaitForMultipleObjects() 等待多个事件
win下互斥量和临界区区别
|
Mutex |
Critical Section |
性能和速度 |
慢。 Mutex 是内核对象,相关函数的执行 (WaitForSingleObject, ReleaseMutex)需要用户模式(User Mode)到内核模式 (Kernel Mode)的转换,在x86处理器上这种转化一般要 发费600个左右的 CPU指令周期。 |
快。 Critical Section本身不是内核对象,相关函数 (EnterCriticalSection,LeaveCriticalSection) 的调用一般都在用户模式内执行,在x86处理器上 一般只需要发费9个左右的 CPU指令周期。只有 当想要获得的锁正好被别的线程拥有时才会退化 成和Mutex一样,即转换到内核模式,发费600个 左右的 CPU指令周期。 |
能否跨越进程(Process)边界 |
可以 |
不可 |
定义写法 |
HANDLE hmtx; |
CRITICAL_SECTION cs; |
初始化写法 |
hmtx= CreateMutex (NULL, FALSE, NULL); |
InitializeCriticalSection(&cs); |
结束清除写法 |
CloseHandle(hmtx); |
DeleteCriticalSection(&cs); |
无限期等待的写法 |
WaitForSingleObject (hmtx, INFINITE); |
EnterCriticalSection(&cs); |
0等待(状态检测)的写法 |
WaitForSingleObject (hmtx, 0); |
TryEnterCriticalSection(&cs); |
任意时间等待的写法 |
WaitForSingleObject (hmtx, dwMilliseconds); |
不支持 |
锁释放的写法 |
ReleaseMutex(hmtx); |
LeaveCriticalSection(&cs); |
能否被一道用于等待其他内核对象 |
可以(使用WaitForMultipleObjects, WaitForMultipleObjectsEx, MsgWaitForMultipleObjects, MsgWaitForMultipleObjectsEx等等) |
不可 |
当拥有锁的线程死亡时 |
Mutex变成abandoned状态,其他的等待线程可以获得锁。 |
Critical Section的状态不可知(undefined), 以后的动作就不能保证了。 |
自己会不会锁住自己 |
不会(对已获得的Mutex,重复调用WaitForSingleObject不会 锁住自己。但最后你别忘了要调用同样次数的 ReleaseMutex) |
不会(对已获得的Critical Section,重复调用 EnterCriticalSection不会锁住自己。但最后 你别忘了要调用同样次数的 LeaveCriticalSection) |