[TOC]
一、ZBar中的多线程
在ZBar中,需要进行大量的数据提取和数据获取,若仅仅进行串行处理,往往会出现运行时间过长等问题。因此ZBar中有许许多多的地方都使用了多线程的处理方式,这在前面对各个模块的解析中有所提及。
在引入多线程后,又有许多问题需要注意,例如线程安全的保证、线程同步的实现方式等等。
下面首先对多线程操作时的几个概念以及ZBar中使用多线程的几个地方进行举例说明。
线程:cpu调度的最小单位
线程共享进程的资源,多个线程可以共享同一地址空间和其他资源,比如共享全局变量。线程作为进程的一部分,扮演的角色就是怎么利用中央处理器去运行代码。线程关注的是中央处理器的运行,而不是内存等资源的管理。同一时刻只有一个线程占用cpu,但高速切换给人带来并行的假象。
- 为什么多线程?
- 线程比进程更加轻量级,线程更容易、快捷的创建和销毁。
- 多CPU系统中,使用线程提高CPU利用率。
- 耗时的操作使用线程,提高应用程序响应。拥有多个线程允许活动彼此重叠进行,从而会加快应用程序执行速度。
- 并行操作时使用线程,如C/S架构的服务器端并发线程响应用户的请求。
- 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
- 并行实体共享同一个地址空间和所有可用数据的能力。

何为线程安全?
我们经常会听说某个类是线程安全,某个类不是线程安全的。那么究竟什么叫做线程安全呢?
我们引用《Java Concurrency in Practice》里面的定义:
在不使用额外同步的情况下,多个线程访问一个对象时,不论线程之间如何交替执行或者在调用方进行任何其它的协调操作,调用这个对象的行为都能得到正确的结果,那么这个对象是线程安全的。
也可以这么理解:
多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
锁机制
通过锁机制,能够保证在多核多线程环境中,在某一个时间点上,只能有一个线程进入临界区代码,从而保证临界区中操作数据的一致性。
所谓的锁,可以理解为内存中的一个整型数,拥有两种状态:空闲状态和上锁状态。加锁时,判断锁是否空闲,如果空闲,修改为上锁状态,返回成功。如果已经上锁,则返回失败。解锁时,则把锁状态修改为空闲状态。
二、ZBar中使用多线程的代码示例
Window线程的上锁与解锁
在渲染窗口时,必须保证渲染函数window.draw是线程安全的。ZBar对于线程安全方面的处理都是采用互斥锁和信号量实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| `
1. static inline int window_lock (zbar_window_t *w) 2. { 3. int rc = 0; 4. if((rc = _zbar_mutex_lock(&w->imglock))) { 5. err_capture(w, SEV_FATAL, ZBAR_ERR_LOCKING, __func__, 6. "unable to acquire lock"); 7. w->err.errnum = rc; 8. return(-1); 9. } 10. return(0); 11. }
13. static inline int window_unlock (zbar_window_t *w) 14. { 15. int rc = 0; 16. if((rc = _zbar_mutex_unlock(&w->imglock))) { 17. err_capture(w, SEV_FATAL, ZBAR_ERR_LOCKING, __func__, 18. "unable to release lock"); 19. w->err.errnum = rc; 20. return(-1); 21. } 22. return(0); 23. }
`

|
当有渲染窗口的线程获得了互斥锁,其他线程则必须等待,直到其释放锁。
Vedio视频流的上锁与解锁
在对视频流进行锁定和解锁时,都必须保证线程安全,即不允许出现互斥情况,即有一个视频流以上同时进入临界区。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
| `
1. /* video.next_image and video.recycle_image have to be thread safe 2. * wrt/other apis 3. */ 4. static inline int video_lock (zbar_video_t *vdo) 5. { 6. int rc = 0; 7. if((rc = _zbar_mutex_lock(&vdo->qlock))) { 8. err_capture(vdo, SEV_FATAL, ZBAR_ERR_LOCKING, __func__, 9. "unable to acquire lock"); 10. vdo->err.errnum = rc; 11. return(-1); 12. } 13. return(0); 14. }
16. static inline int video_unlock (zbar_video_t *vdo) 17. { 18. int rc = 0; 19. if((rc = _zbar_mutex_unlock(&vdo->qlock))) { 20. err_capture(vdo, SEV_FATAL, ZBAR_ERR_LOCKING, __func__, 21. "unable to release lock"); 22. vdo->err.errnum = rc; 23. return(-1); 24. } 25. return(0); 26. }
28. static inline int video_nq_image (zbar_video_t *vdo, 29. zbar_image_t *img) 30. { 31. /* maintains queued buffers in order */ 32. img->next = NULL; 33. if(vdo->nq_image) 34. vdo->nq_image->next = img; 35. vdo->nq_image = img; 36. if(!vdo->dq_image) 37. vdo->dq_image = img; 38. return(video_unlock(vdo)); 39. }
`

|
可以看到,ZBar实现线程安全的方式是使用互斥锁,下面对ZBar互斥锁的实现进行分析。
三、源码分析
从上面的举例中可以看到,ZBar使用互斥锁时使用的核心函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| `
1. typedef pthread_mutex_t zbar_mutex_t;
3. static inline int _zbar_mutex_init (zbar_mutex_t *lock) 4. { 5. # ifdef DEBUG_LOCKS 6. pthread_mutexattr_t attr; 7. pthread_mutexattr_init(&attr); 8. pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); 9. int rc = pthread_mutex_init(lock, &attr); 10. pthread_mutexattr_destroy(&attr); 11. return(rc); 12. # else 13. return(pthread_mutex_init(lock, NULL)); 14. # endif 15. }
17. static inline void _zbar_mutex_destroy (zbar_mutex_t *lock) 18. { 19. pthread_mutex_destroy(lock); 20. }
22. static inline int _zbar_mutex_lock (zbar_mutex_t *lock) 23. { 24. int rc = pthread_mutex_lock(lock); 25. # ifdef DEBUG_LOCKS 26. assert(!rc); 27. # endif 28. /* FIXME save system code */ 29. /*rc = err_capture(proc, SEV_ERROR, ZBAR_ERR_LOCKING, __func__, 30. "unable to lock processor");*/ 31. return(rc); 32. }
34. static inline int _zbar_mutex_unlock (zbar_mutex_t *lock) 35. { 36. int rc = pthread_mutex_unlock(lock); 37. # ifdef DEBUG_LOCKS 38. assert(!rc); 39. # endif 40. /* FIXME save system code */ 41. return(rc); 42. }
`

|
使用函数解析——pthread_mutex_init
1 2 3 4 5 6 7
| 1. int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr); 3. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
函数作用:
该函数用于C函数的多线程编程中,互斥锁的初始化。
pthread_mutex_init() 函数是以动态方式创建互斥锁的,参数attr指定了新建互斥锁的属性。如果参数attr为空,则使用默认的互斥锁属性,默认属性为快速互斥锁 。互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。
pthread_mutexattr_init() 函数成功完成之后会返回零,其他任何返回值都表示出现了错误。
函数成功执行后,互斥锁被初始化为未锁住态。
互斥锁pthread_mutex_t的使用:
1. 互斥锁创建
有两种方法创建互斥锁,静态方式和动态方式,ZBar中采用的是后者。
静态初始化互斥锁方法如下:
1
| pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
|
动态方式是采用pthread_mutex_init()函数来初始化互斥锁,API定义如下:
1
| int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
|
其中mutexattr用于指定互斥锁属性(见下),如果为NULL则使用缺省属性。
pthread_mutex_destroy ()用于注销一个互斥锁,API定义如下:
使用函数解析——pthread_mutex_destroy
1 2 3 4 5 6 7 8 9 10 11
| 1. static inline void _zbar_mutex_destroy (zbar_mutex_t *lock) 2. { 3. pthread_mutex_destroy(lock); 4. }
|
销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。
互斥锁属性
互斥锁的属性在创建锁的时候指定,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。当前有四个值可供选择:
* PTHREAD_MUTEX_TIMED_NP
缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
* PTHREAD_MUTEX_RECURSIVE_NP
嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
* PTHREAD_MUTEX_ERRORCHECK_NP
检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。(这正是ZBar使用的互斥锁类型)
1
| pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
|
* PTHREAD_MUTEX_ADAPTIVE_NP
适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
锁操作
锁操作主要包括:
1 2 3 4 5 6 7 8 9 10 11
| 1. int pthread_mutex_lock(pthread_mutex_t *mutex)
3. int pthread_mutex_unlock(pthread_mutex_t *mutex)
5. int pthread_mutex_trylock(pthread_mutex_t *mutex)
|
pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。
不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。
对于普通锁和适应锁类型,解锁者可以是同进程内任何线程;
而检错锁则必须由加锁者解锁才有效,否则返回EPERM;
对于嵌套锁,文档和实现要求必须由加锁者解锁,但实验结果表明并没有这种限制,这个不同目前还没有得到解释。
在同一进程中的线程,如果加锁后没有解锁,则任何其他线程都无法再获得锁。
三、总结
本次代码分析对ZBar中线程安全的实现方式以及使用函数进行了详细分析,这也是对之前代码分析中一些缺漏的补充说明。