1.如何避免死锁
当两个线程需要两个互斥量,如线程一锁住互斥量A需要互斥量B,但线程二锁住互斥量B需要互斥量A,那么就会发生死锁。解决方法1)控制互斥量的枷锁顺序,即不上上述情况发生。2)如果程序复杂,那么可以尝试先施放当前锁,过一段时间在试试。可用pthread_mutex_trylock函数,此函数当锁一个互斥量不成功的时候会立即的返回,而不会像pthread_mutext_lock一样阻塞,如果返回不成功那么可以先释放以前锁住的互斥量,做好清理工作,过一段时间在来尝试。
2.锁粒度的问题
在枷锁的时候,要考虑锁的粒度,如果锁的粒度太大,可能会出现很多的线程等待同一个锁的情况,那么源自并发性的改善微乎其微。如果粒度太小,那么过多的锁开销会使系统的性能受到严重的影响,而且代码变得相当的复杂,在满足锁需求的情况下,代码复杂度和优化性能之前要找好平衡点。
3.mutex与rwlock
在一定的条件下,读写锁比互斥量允许更高的并行性,互斥量只有两种状态,枷锁和不加锁,但是读写锁有三种状态,读模式下加锁,写模式下加锁,和不加锁。一次只有一个进程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。线程如果希望以写模式加锁的时候,那么它必须阻塞直到所有的线程都释放读锁(这就保证了读出来的数据的一致性),但是当有写模式锁请求的时候,会阻塞以后的读模式锁的请求防止读模式锁被长期占用而写模式的锁得不到请求。适用于对数据结构读得次数远大于写的次数的情况,当任务频繁的时候,读任务次数很多,因为使用读模式的锁,可以同时读取任务,所以可以明显提高速度,而不用使得线程处于阻塞的状态。
读写锁一个具体的应用如下,任务队列会一次添加大量的任务,每个任务都有一个id,每个id不同且增长,每个任务还有一个线程id的属性,那么主线程可以预先创建一批线程,并且把线程的tid存储下来,在分发任务的时候加上线程的tid。而预先创建的线程可以从头到尾的读取主线程的任务队列,并且记录下已经完成任务id的最大值,当task的tid与自己tid相等,且task的id大于存储的id的时候处理这个任务。(这样,不同的tid的线程,任务可以不同,还可以同时读取任务)。
这里的“读”是只从队列中读取,并不删除队列的内容,删除队列的内容的时候也需要写锁。
4.cond
条件变量与互斥量一起使用从可以允许线程以无竞争的方式等待特定的条件发生。为什么必须一起使用呢?1)假如当某个资源满足了一定的条件时它要发送信号告诉需要它的线程,假如现在资源为空,条件不满足,那么线程就要等待,那么在线程条件检查时刻t1,到线程放到表上的时刻t2,在t1到t2这段时间内,可能资源来了,然而CPU调度到另外的工作进程上了,这样条件就不能改变,这样线程就错过了条件变化,虽然等线程放到队列上后,资源变化可以通知线程,但是会有时间延迟。2)传递给pthread_cond_wait(pthread_cont_t *restrict cond, pthread_mutex_t *restrict mutex)中的mutex必须是已经锁住的,如果这个互斥锁不是锁住的,那么假如满足条件,线程一对资源访问的同时线程二也可能访问,就造成了资源的不一致。所以这个过程还是挺复杂的,当需要等待时,把一个资源的互斥量和等待条件一同传给这个wait函数,这个wait函数把线程放到这个条件的等待队列上,然后对互斥量解锁(注意这两步是个原子操作,具体原因如1)。解锁一来可以让条件发生(如果死守着资源,那么资源也就不会变了),二来可以让其他线程同时访问资源,同样可以检查条件。
5.线程安全
如果一个函数在同一时刻可以被多个线程安全的调用,那么就说这个函数是线程安全的。大多数的函数是线程安全的,但是操作系统里有一部分函数不是的。
6.线程中调用fork
在一个进程中,有多个线程,其中的一个线程调用fork的话,产生的子进程如果不立即调用exec的话,就需要清理状态锁,因为子进程内部只有一个线程,它是由父进程调用fork的线程副本构成的。