相比多进程模型,多线程模型最大的优势在于数据共享非常方便,同一进程内的多个线程可以使用相同的地址值访问同一块内存数据。但是,当多个线程对同一块内存数据执行“读−处理−更新”操作时,会由于线程的交叉执行而造成数据的错误。 例如以下代码段,当 thread_func() 同时在多个线程中执行时,更新到 glob_value 中的值就会互相干扰,产生错误结果。
解决这类问题的关键在于,当一个线程正在执行“读−处理−更新”操作时,保证其他线程不会中途闯入与其交叉执行。不可被打断的执行序列称为临界区,保证多个线程不会交叉执行同一临界区的技术称为线程同步。 1 互斥锁的使用最常用的线程同步技术是互斥锁,Linux 线程库中的相关函数有:
这里pthread的p代表POSIX线程 所有线程都有一个线程号,也就是Thread ID。其类型为pthread_t。通过调用pthread_self()函数可以获得自身的线程号。 pthread_mutex_lock() 负责在进入临界区之前对临界区加锁; 当某个线程试图给一个已经处在加锁状态的临界区再次加锁时,该线程就会被临时挂起,一直等到该临界区被解锁后,才会被唤醒并继续执行。 如果同时有多个线程等待某个临界区解锁,那下次被唤醒的进程取决于内核的调度策略,并没有固定的顺序。 静态分配的 mutex 变量在使用之前应该被初始化为 PTHREAD_MUTEX_INITIALIZER,而动态分配的 mutex 需要调用 pthread_mutex_init() 进行初始化,且只被某个线程初始化一次,可以利用 pthread_once() 函数方便完成。
多个线程在临界区上的执行是串行的,开发者应该尽量减少程序在临界区内的停留时间,以提高程序的并行性。因此,临界区不应该包含任何非必须的逻辑,以及任何可能带来高延迟的 IO 等操作 2 互斥锁的保护范围和使用顺序对互斥锁加锁的不恰当使用会造成线程的死锁,比如下面这两种情况。
因此,开发者需要仔细规划互斥锁保护范围和使用顺序 3 避免死锁的两个加锁函数为了避免出现死锁问题,可以使用另外两种变体的锁定函数,如下所示:
前者可以在锁定失败后立即返回,后者可以在一段超时时间后返回, 应用这两个函数可以处理这种错误情况,而避免陷入无限的死锁中。 在 Linux 中,实现互斥锁采用的是 Futex(Fast Userspace Mutex)方案。在该实现中,只有发生了锁的争用才需要陷入到内核空间中处理,否则所有的操作都可以在用户空间内快速完成。在大多数情况下,互斥锁本身的效率很高,其平均开销大约相当于几十次内存读写和算数运算所花费的时间。 来源:http://www./content-4-220351.html |
|