感谢支持
我们一直在努力

Linux多线程处理知识

多线程的竞争条件:


当一个线程访问一个数据结构的时候,另一个线程也访问同一个数据结构,这时就出现了竞争条件——两个线程(也可能是多个)竞争对同一个资源的访问。


当其中一个线程处理到一部分的时候,另外的线程可能进入了对同一数据的处理,而且出于调度的原因,它运行的比前一个更快;这时,同样的处理可能就出现了多次。


例如,一个代表任务列表的单向链表(队列),一个线程从当前元素中读出了下一个任务,并发现下一个任务不是空,准备将此一个任务置为NULL并执行任务;这时,调度使得这个线程停下来,另一个线程也从当前元素中读出了下一个任务,当然下个任务仍然不是空值,这个线程也将要执行下一个任务。这样,在某些不幸的情况下,这个任务被执行了两次。


更为不幸的情况下,执行任务的过程中线程被中断,此时另一个线程释放了任务的内存,那么执行中的线程会导致段错误。在比较“幸运”的情况下,这些事情可能从来也不发生;但是当程序运行在负荷很高的系统中时,这个bug就会凸现出来。


互斥锁:


为了排除竞争条件的影响,应该使一些操作变成“原子的”——这些操作既不能分割也不能中断。


当一个线程锁定了互斥锁后,其他线程也要求锁定这个互斥锁的时候,就会被阻塞;直到前面的线程解除锁定后,其他线程才可以解除阻塞恢复运行。GNU/Linux保证线程在锁定互斥锁的过程中不会发生竞争条件,只有一个线程可以锁定互斥锁,其他线程的锁定请求都会被阻塞。


创建互斥锁(Mutex),需要:


创建一个pthread_mutex_t类型的变量,将其指针传入函数pthread_mutex_init中;该函数的第二个参数是指向一个mutex属性对象(这个对象用来指定mutex的属性)的指针。mutex对象只能初始化一次。


更简单的办法是,使用PTHREAD_MUTEX_INITIALIZER来获得默认属性的mutex对象:


pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;


使用mutex:


调用pthread_mutex_lock函数,如果mutex对象未被锁定,则此函数立即返回;如果已被锁定,则此函数阻塞此线程的执行,直到mutex被解除锁定。每次解除锁定后,只有一个线程可以解除阻塞恢复执行,其他调用线程都会继续阻塞。选定的解除阻塞的线程是不可预知的。调用pthread_mutex_unlock函数,可以解除调用线程对mutex对象的锁定。在锁定mutex的线程中,必须调用此函数以解除锁定。


mutex提供了解决竞争条件的方案,但是它带来了一种新的bug——死锁(deadlock)。


所谓死锁,就是说一个线程在等待永远不会发生的条件。


一个简单的死锁:一个线程锁定它自己已经锁定的mutex。这时程序的表现依赖于mutex的类型:


快速排他锁(fast mutex)——导致死锁。


递归排他锁(recursive mutex)——不导致死锁。同一个线程可以安全的多次锁定同一个已锁定的mutex,但是锁定的次数会被记录下来,该线程还必须调用相应次数的pthread_mutex_unlock才能真正解除对mutex的锁定。


检错排他锁(error-checking mutex)——GNU/Linux视此操作为死锁,但是对锁定的mutex调用pthread_mutex_lock函数,函数立即返回错误码EDEADLK。


mutex的锁定函数会阻塞,有时需要一个不阻塞就能知道mutex是否已锁定的函数,以在发现mutex已锁的情况下去完成其他工作并在以后再来检查。这样的函数是:pthread_mutex_trylock(),如果传入的mutex已经被其他线程锁定,那么这个函数返回错误码EBUSY;如果未被锁定,此函数会锁定mutex,并返回0。这个函数不会阻塞。

进一步补充:


pthread_mutex_lock, pthread_mutex_trylock, pthread_mutex_unlock – lock and unlock a mutex


函数原型


#include <pthread.h>  int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex);


描述


pthread_mutex_lock()函数锁住由mutex指定的mutex 对象。如果mutex已经被锁住,调用这个函数的线程阻塞直到mutex可用为止。这跟函数返回的时候参数mutex指定的mutex对象变成锁住状态,同时该函数的调用线程成为该mutex对象的拥有者。


如果mutex 对象的type是 PTHREAD_MUTEX_NORMAL,不进行deadlock detection(死锁检测)。企图进行relock 这个mutex会导致deadlock. 如果一个线程对未加锁的或已经unlock的mutex对象进行unlock操作,结果是不未知的。


如果mutex类型是 PTHREAD_MUTEX_ERRORCHECK,那么将进行错误检查。如果一个线程企图对一个已经锁住的mutex进行relock,将返回一个错误。如果一个线程对未加锁的或已经unlock的mutex对象进行unlock操作,将返回一个错误。


如果mutex类型是 PTHREAD_MUTEX_RECURSIVE,mutex会有一个锁住次数(lock count)的概念。当一个线程成功地第一次锁住一个mutex的时候,锁住次数(lock count)被设置为1,每一次一个线程unlock这个mutex的时候,锁住次数(lock count)就减1。当锁住次数(lock count)减少为0的时候,其他线程就能获得该mutex锁了。如果一个线程对未加锁的或已经unlock的mutex对象进行unlock操作,将返回一个错误。


如果mutex类型是 PTHREAD_MUTEX_DEFAULT,企图递归的获取这个mutex的锁的结果是不确定的。unlock一个不是被调用线程锁住的mutex的结果也是不确定的。企图unlock一个未被锁住的mutex导致不确定的结果。


pthread_mutex_trylock()调用在参数mutex指定的mutex对象当前被锁住的时候立即返回,除此之外,pthread_mutex_trylock()跟pthread_mutex_lock()功能完全一样。


The pthread_mutex_unlock()函数释放有参数mutex指定的mutex对象的锁。如果被释放取决于该Mutex对象的类型属性。如果有多个线程为了获得该mutex锁阻塞,调用pthread_mutex_unlock()将是该mutex可用,一定的调度策略将被用来决定哪个线程可以获得该mutex锁。(在mutex类型为PTHREAD_MUTEX_RECURSIVE 的情况下,只有当lock count 减为0并且调用线程在该mutex上已经没有锁的时候)(翻译到这里,才觉得我的这个锁概念是多么模糊)


如果一个线程在等待一个mutex锁得时候收到了一个signal,那么在从signal handler返回的时候,该线程继续等待该mutex锁,就像这个线程没有被中断一样。


返回值


成功,pthread_mutex_lock() 和 pthread_mutex_unlock() 返回0,否则返回一个错误的提示码


pthread_mutex_trylock() 在成功获得了一个mutex的锁后返回0,否则返回一个错误提示码


错误


pthread_mutex_lock() 和 pthread_mutex_unlock()失败的时候


[EINVAL]


mutex在生成的时候,它的protocol属性的值是 PTHREAD_PRIO_PROTECT,同时调用线程的优先级(priority)比该mutex的当前prority上限高


pthread_mutex_trylock() 函数在一下情况会失败:


[EBUSY]


The mutex could not be acquired because it was already locked.


mutex已经被锁住的时候无法再获取锁


The pthread_mutex_lock(), pthread_mutex_trylock() and pthread_mutex_unlock() functions may fail if:


[EINVAL]


mutex指向的mutex未被初始化


[EAGAIN]


Mutex的lock count(锁数量)已经超过递归索的最大值,无法再获得该mutex锁


pthread_mutex_lock() 函数在一下情况下会失败:


[EDEADLK]


当前线程已经获得该mutex锁


pthread_mutex_unlock() 函数在以下情况下会失败:


[EPERM]


当前线程不是该mutex锁的拥有者


所有的这些函数的错误返回值都不会是[EINTR]

赞(0) 打赏
转载请注明出处:服务器评测 » Linux多线程处理知识
分享到: 更多 (0)

听说打赏我的人,都进福布斯排行榜啦!

支付宝扫一扫打赏

微信扫一扫打赏