Linux 上多线程编程经验

来源:互联网 发布:linux 串口调试助手 编辑:程序博客网 时间:2024/06/06 18:45


本文中我们针对 Linux 上多线程编程的主要特性总结出 5 条经验,用以改善 Linux 多线程编程的习惯和避免其中的开发陷阱。
熟悉 Linux 平台的多线程编程
熟悉 Linux 平台上基本的线程编程的 Pthread 库 API
多线程开发的最基本概念主要包含三点:
一.线程控制
1. 创建----pthread_create
2. 退出----pthread_exit
3. 等待----pthread_join

二.线程同步之互斥锁
1. 创建----pthread_mutex_init
2. 销毁----pthread_mutex_detroy
3. 加锁----pthread_mutex_lock
4. 解锁----pthread_mutex_unlock

三.线程同步之条件变量
1. 创建----pthread_cond_init
2. 销毁----pthread_cond_detroy
3. 单触发--pthread_cond_signal
4. 广播----pthread_cond_broadcast
5. 等待----pthread_cond_wait/pthread_cond_timewait

******Linux 线程编程中的 5 条经验*******

经验 1. Linux 重复对互斥锁加锁实例
在Linux下无法对同一线程中试图对互斥锁进行两次或两次以上的行为
代码如下;
    // 通过默认条件建锁
    pthread_mutex_t *theMutex = new pthread_mutex_t;
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutex_init(theMutex,&attr);
    pthread_mutexattr_destroy(&attr);

    // 递归加锁
    pthread_mutex_lock (theMutex);
    pthread_mutex_lock (theMutex);
    pthread_mutex_unlock (theMutex);
    pthread_mutex_unlock (theMutex);
在以上代码场景中,问题将出现在第二次加锁操作。由于在默认情况下,Linux 不允许同一线程递归加锁,因此在第二次加锁操作时线程将出现死锁。
解决办法:显式地在互斥变量初始化时将设置起 recursive 属性。
可以解决同一线程递归加锁的问题,又可以避免很多情况下死锁的发生。
修改后如下:
    pthread_mutexattr_init(&attr);
    // 设置 recursive 属性
    pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE_NP);
    pthread_mutex_init(theMutex,&attr);
经验2.注意 Linux 平台上触发条件变量的自动复位问题
Linux 平台的 Pthread (pthread_cond_signal)所采用的模型,当条件变量置位(signaled)以后,不管当前有没有任何线程在等待,其状态也会恢复为复位(unsignaled)状态。
由于这种差异只发生在触发没有被线程等待在条件变量的时刻,因此我们只需要掌握好触发的时机即可。最简单的做法是增加一个计数器记录等待线程的个数,在决定触发条件变量前检查下该变量即可。
建议在 Linux 平台上要出发条件变量之前要检查是否有等待的线程,只有当有线程在等待时才对条件变量进行触发。
经验3.注意条件返回时互斥锁的解锁问题
在 Linux 调用 pthread_cond_wait 进行条件变量等待操作时,我们增加一个互斥变量参数是必要的,这是为了避免线程间的竞争和饥饿情况。但是当条件等待返回时候,需要注意的是一定不要遗漏对互斥变量进行解锁。
Linux 平台上的 pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) 函数返回时,互斥锁 mutex 将处于锁定状态。因此之后如果需要对临界区数据进行重新访问,则没有必要对 mutex 就行重新加锁。但是,随之而来的问题是,每次条件等待以后需要加入一步手动的解锁操作。
经验4.等待的绝对时间问题
超时是多线程编程中一个常见的概念。例如,当你在 Linux 平台下使用 pthread_cond_timedwait() 时就需要指定超时这个参数,以便这个 API 的调用者最多只被阻塞指定的时间间隔。但是如果你是第一次使用这个 API 时,首先你需要了解的就是这个 API 当中超时参数的特殊性(就如本节标题所提示的那样)。我们首先来看一下这个 API 的定义。 pthread_cond_timedwait() 。
pthread_cond_timedwait() 函数定义如下:
    int pthread_cond_timedwait(pthread_cond_t *restrict cond,
              pthread_mutex_t *restrict mutex,
              const struct timespec *restrict abstime);
Linux 的绝对时间看似简单明了,却是开发中一个非常隐晦的陷阱。而且一旦你忘了时间转换,可以想象,等待你的错误将是多么的令人头疼:如果忘了把相对时间转换成绝对时间,相当于你告诉系统你所等待的超时时间是过去式的 1970 年 1 月 1 号某个时间段,于是操作系统毫不犹豫马上送给你一个 timeout 的返回值,然后你会举着拳头抱怨为什么另外一个同步线程耗时居然如此之久,并一头扎进寻找耗时原因的深渊里。
经验5.正确处理 Linux 平台下的线程结束问题
在 Linux 平台下,当处理线程结束时需要注意的一个问题就是如何让一个线程善始善终,让其所占资源得到正确释放。在 Linux 平台默认情况下,虽然各个线程之间是相互独立的,一个线程的终止不会去通知或影响其他的线程。但是已经终止的线程的资源并不会随着线程的终止而得到释放,我们需要调用 pthread_join() 来获得另一个线程的终止状态并且释放该线程所占的资源。
调用该函数的线程将挂起,等待 th 所表示的线程的结束。 thread_return 是指向线程 th 返回值的指针。需要注意的是 th 所表示的线程必须是 joinable 的,即处于非 detached(游离)状态;并且只可以有唯一的一个线程对 th 调用 pthread_join() 。如果 th 处于 detached 状态,那么对 th 的 pthread_join() 调用将返回错误。
如果你压根儿不关心一个线程的结束状态,那么也可以将一个线程设置为 detached 状态,从而来让操作系统在该线程结束时来回收它所占的资源。将一个线程设置为 detached 状态可以通过两种方式来实现。一种是调用 pthread_detach() 函数,可以将线程 th 设置为 detached 状态。
总之为了在使用 Pthread 时避免线程的资源在线程结束时不能得到正确释放,从而避免产生潜在的内存泄漏问题,在对待线程结束时,要确保该线程处于 detached 状态,否着就需要调用 pthread_join() 函数来对其进行资源回收。




0 0
原创粉丝点击