Linux_线程的同步

线程的同步

线程的同步即线程之间对临界资源访问的时序合理性。当多个控制线程共享相同的内存时,需要确保每个线程看到一致的数据视图,如果每个线程使用的变量都是其他线程不会读取或者修改的,就不会存在一致性问题, 否则就需要注意同步问题。通常来说用户可以使用互斥量或者条件变量方式来解决线程的同步问题。

一、使用互斥锁解决线程的同步

mutex(互斥量)
大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。对于线程来说,整个地址空间都是共享的资源,所以线程的任何资源都是共享的。 多个线程并发的操作共享资源,就会带来一些问题。

互斥锁是一个简单的锁定命令,它可以用来锁定对共享资源的访问。

互斥锁具有以下三个主要特点:

  • 原子性:把一个互斥锁定为一个原子操作,这意味着操作系统(或 pthread 函数库)保证了如果一个线程锁定了一个互斥锁,则没有其他线程可以在同一时间成功锁定这个互斥锁。
  • 唯一性:如果一个线程锁定一个互斥锁,在它解除锁定之前,没有其他线程可以锁定这个互斥量。
  • 非繁忙等待:如果一个线程已经锁定了一个互斥锁,第二个线程又试图去锁定这个互斥锁,则第二个线程将被挂起(不占用任何 CPU 资源),直到第一个线程解除对这个互斥锁的锁定为止,第二个线程则被唤醒并继续执行,同时锁定这个互斥锁。

Linux内核提供了相应的函数来完成对应的操作。

1. 互斥锁的初始化函数

初始化互斥量有两种方法:

方法一:静态分配

#include <pthread.h>
pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;      
pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;      
pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
  • 上面三个常量是常用的处理互斥锁的常量。
  • 不会出现有多个线程同时初始化同一个互斥锁的情形,一个互斥锁在使用期间定不会被重新初始化。

方法二:动态分配
pthread_mutex_init 函数用来初始化一个由参数 mutex 指向的互斥锁,这个互斥锁的属性由参数 attr 指定,或者通过指定 attr 为 NULL 而使用默认的属性。

#include <pthread.h>      
int pthread mutex init(pthread_mutex_t *mutex, const pthread_mutex_attr *attr);
  • 如果 pthread_mutex_init 执行成功,则返回 0,并将新创建的互斥锁的 ID 值放到参数 mutex 中。如果执行失败,返回一个错误编号。

2. 互斥解除函数

pthread_mutex_destroy 函数用于解除由参数 mutex 指向的互斥锁的任何状态。

#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex)
  • 存储互斥锁的内存并不被释放。
  • 如果 pthread_mutex_destroy 执行成功,则返回 0,如果执行失败,返回一个错误编号。
  • 使用静态分配初始化的互斥量不需要销毁。
  • 不要销毁一个已经加锁的互斥量。
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁。

3. 互斥锁锁定函数

pthread_mutex_lock 函数可以用于锁定由参数 mutex 指向的互斥锁。

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex); 
  • 如果 mutex 处于未锁状态,该函数会将其锁定,同时返回成功。
  • 如果 mutex 已经被锁定,那么当前调用的线程将阻塞直到互斥锁被其他线程释放(阻塞线程按照线程优先级等待)。
  • 当 pthread_mutex_lock 返回时,说明互斥锁已经被当前的线程成功加锁。
  • 如果pthread_mutex_lock 执行成功,则返回 0,其他值说明发生了错误。

4. 互斥锁加锁函数

pthread_mutex_trylock 函数用于尝试给由参数 mutex 指定的互斥锁加锁。

#include <pthread.h>      
int pthread_mutex_trylock(pthread_mutex_t *mutex); 
  • 该函数是pthread_mutex_lock的非阻塞版本。pthread_mutex_lock 在给一个互斥锁加锁时,如果互斥锁已经被锁定,那么 pthread_mutex_lock 将一直阻塞,不会立即返回。
  • 使用pthread_mutex_trylock 给一个互斥锁加锁时,如果互斥锁已经被锁定,那么 pthread_mutex_trylock 调用将返回错误,否则,互斥锁将被调用者加锁。
  • 如果 pthread_mutex_trylock 执行成功,则返回 0,其他值说明发生了错误。

5. 互斥锁解锁函数

pthread_mutex_unlock 函数给由参数 mutex 指定的互斥锁解锁。

#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • 互斥锁必须处于加锁状态,而且调用本函数的线程必须是给互斥锁加锁的同一个线程才能给互斥锁解锁。
  • 如果有其他线程在等待互斥锁,那么由核心的调度程序决定哪个线程将获得互斥锁并脱离阻塞状态。
  • 如果 pthread_mutex_unlock 执行成功,则返回0,其他值说明发生了错误。

二、使用条件变量解决线程同步

在程序中使用互斥锁虽然可以解决一些资源竞争的问题,但是互斥锁只有两种状态,这使得它的用途非常有限。除了互斥锁之外,还可以使用条件变量来解决线程的同步问题,条件变量是对互斥锁的补充,它允许线程阻塞并等待另一个线程发送的信号。当收到信号时,阻塞的线程就被唤醒并试图锁定与之相关的互斥锁。

条件变量:

  • 当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
  • 例如一个线程访问队列时,发现队列为空,它只能等待,直到其它线程将一个节点添加到队列中。 这种情况就需要用到条件变量。

Linux 提供了相应的函数来完成对应的操作。

1. 条件变量初始化函数

pthread_cond_init 函数用于初始化由参数 cond 指定的条件变量。

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cond,const pthread_cond_attr *attr); 
  • 参数:
    cond:要初始化的条件变量
    attr:NULL

  • 这个条件变量的属性由参数 attr 指定。如果参数 attr 为 NULL,那么就使用默认的属性设置。

  • 多线程不能同时初始化同一个条件变量,如果一个条件变量正在使用, 则它不能被重新初始化

  • 如果 pthread_cond_init 执行成功,则返回 0,并将新创建的条件变量的ID放在参数 cond 中,如果返回其他的值则意味着有错误。

2. 条件变量解除函数

pthread_cond_destroy 函数用于清除由参数 cond 指向的条件变量的任何状态。

#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
  • 存储条件变量的内存空间不被释放.
  • 如果函数 pthread_cond_destroy 执行成功则返回0,其他值说明发生了错误。

3. 条件变量阻塞函数

使用 pthread_cond_wait 函数释放由参数 mutex 指向的互斥锁。

#include <pthread.h>      
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
  • 被阻塞的线程可以被pthread_cond_signal、 pthread _cond_broadcast 或者由 fork 和传递信号引起的中断唤醒。
  • 即使返回错误信息,pthread_cond_wait 通常在互斥锁被调用线程加锁后才返回。函数将阻塞直到条件变量被信号唤醒,它在阻塞前自动释放互斥锁,在返回前再自动获得它。如果有多个线程关于条件变量阻塞,则其退出阻塞状态的顺序将不确定。
  • 如果 pthread_cond_wait 执行成功则返回 0,其他值说明发生了错误。

4. 带时间的条件变量阻塞函数

#include <pthread.h>      
int pthread_cond_timewait(pthread_cond_t *cond, pthread_mutex_t *mutex,const struct timespec *abstime);
  • pthread_cond_timewait 和 pthread_cond_wait 的用法相似,区别在于pthread_cond_timewait 在经过由参数 abstime 指定的时间时不阻塞。
  • 即使是返回错误,pthread_cond_timewait 也只在给互斥锁加锁后返回。
  • pthread_cond_timewait 函数将阻塞,直到条件变量获得信号或者经过由 abtimne 指定的时间。
  • 如果 pthread_cond_timewait 执行成功则返回0。如果阻塞条件变量的时间超过了由参数 abstime所指定的时间,那么就返回ETIMEOUT,其他值说明发生了错误。

5. 单个条件变量阻塞退出函数

pthread_cond_signal 函数使得由参数 cond 指向的条件变量阻塞的线程退出阻塞状态。

#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
  • 在同一个互斥锁的保护下使用pthread_cond_signal, 否则,条件变量可以在对关联条件变量的测试和 pthread_cond_wait 带来的阻塞之间获得信号,这将导致无限期的等待。
  • 如果没有一个线程关于条件变量阻塞,那么 pthread_cond_signal 无效。
  • 如果 pthread_cond_signal 执行成功则返回0,其他值说明发生了错误。

6. 全部条件变量阻塞退出函数

pthread_cond_broadcast 函数使得所有由参数cond指向的条件变量阻塞的线程退出阻塞状态。

#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);
  • 如果没有阻塞的线程,则 cond_broadcast 无效。
  • 这个函数将唤醒所有由 pthread_cond_wait 阻塞的线程,因为所有关于条件变量阻塞的线程都同时参与竞争,所以使用这个函数时需要小心。
  • 如果 pthread_cond_broadcast 执行成功则返回 0,其他值说明发生了错误。
发布了71 篇原创文章 · 获赞 131 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_43239560/article/details/100592155
今日推荐