同步调度

如果有约束,尤其是时间约束时(即需要某些线程执行得比其他线程快),程序员可以控制线程的执行调度。

像互斥对象这样的 同步对象甚至会阻塞高优先级的线程。 在某些情况下,可能发生称为优先级反转的不期望行为。 线程库 提供互斥协议工具来避免优先级反转。

同步调度定义如何通过拥有互斥对象修改线程的执行调度,尤其是线程的优先级。 这将允许用户定义的行为并避免优先级反转。 当使用复杂的锁定方案时这将是十分有用的。 线程库的某些实现不提供同步调度。

优先级反转

当低优先级的线程拥有互斥对象而阻塞高优先级的线程时将发生优先级反转。 由于它的优先级较低,所以互斥对象所有者可能会在无限的持续时间内拥有该互斥对象。 结果是,不可能保证线程的截止期限。

以下示例举例说明了一个典型的优先级反转。 在该示例中,考虑一个单处理器系统的情况。 优先级反转还以同样的方式发生在多处理器系统中。

在我们的示例中,使用互斥对象 M 来保护某些公共数据。 线程 A 的优先级为 100 并且经常被调度。 线程 B 优先级为 20,它是一个 后台线程。 进程中的其他线程的优先级接近 60。 来自线程 A 的代码片段如下:
pthread_mutex_lock(&M);             /* 1 */
...
pthread_mutex_unlock(&M);
来自线程 B 的代码片段 如下:
pthread_mutex_lock(&M);          /* 2 */
...
fprintf(...);                    /* 3 */
...
pthread_mutex_unlock(&M);

考虑以下执行时间表。 已调度线程 B 并执行行 2。 执行行 3 时,线程 B 被线程 A抢占先机。 线程 A 执行行 1 并被阻塞,因为互斥对象 M 由线程 B持有。 因此,将调度进程中的其他线程。 由于线程 B 优先级非常低,所以它在很长时间内都不会被重新调度,从而阻塞线程 A(尽管线程 A 有很高的优先级)。

互斥协议

要避免优先级反转,线程库提供以下互斥协议:

优先级继承协议
有时候称为基本优先级继承协议。 在优先级继承协议中,互斥对象所有者继承具有最高优先级的已阻塞线程 的优先级。 当线程尝试使用该协议锁定互斥对象并且该线程是阻塞的线程,如果已阻塞线程的优先级高于所有者的优先级,那么互斥对象所有者临时 接收已阻塞线程的优先级。 当线程将互斥对象解锁时将恢复其最初的优先级。
优先级保护协议
有时候称为优先级最高限度协议仿真。 在优先级保护协议中,每个互斥对象都有一个优先级最高限度。 它是优先级的有效范围内的优先级级别。 当线程拥有一个互斥对象时,如果互斥对象的最高 限度高于它自身的优先级,那么该线程临时接收互斥优先级最高限度。 当线程将互斥对象解锁时将恢复其最初的优先级。 优先级最高限度应该 具有可以锁定互斥对象的所有线程的最高优先级的值。 否则,可能发生优先级反转或甚至是死锁,并且该协议将会效率低下。

两个协议都增加了拥有特定互斥对象的线程的优先级,这样就可以保证截止期限。 此外,如果正确使用的话,那么互斥协议还可以防止相互死锁。 分别将互斥协议指定给每个互斥对象。

选择互斥协议

创建互斥对象时通过设置属性来选择互斥协议。 通过协议属性来控制互斥协议。 使 用 pthread_mutexattr_getprotocolpthread_mutexattr_setprotocol 子例程可以在互斥属性对象中设置该属性。 协议属性可以是以下值之一:
描述
PTHREAD_PRIO_DEFAULT 无任何值
PTHREAD_PRIO_NONE 表示没有协议。
PTHREAD_PRIO_INHERIT 表示优先级继承协议。
PTHREAD_PRIO_PROTECT 表示优先级保护协议。
注: PTHREAD_PRIO_DEFAULT 的行为与 PTHREAD_PRIO_INHERIT 属性相同。 通过引用互斥锁定,使用缺省属性执行的线程将在锁定用户时临时 boost 互斥占位符的优先级,并且其优先级高于所有者。 因此,尽管属性结构中可能的优先级具有四个值,但是只有三个可能的行为。

优先级保护协议使用一个附加属性:prioceiling 属性。 该属性中包含互斥对象的优先级最高限度。 通过 使用 pthread_mutexattr_getprioceilingpthread_mutexattr_setprioceiling 子例程在互斥属性对象中控制 prioceiling 属性。

还可以使用 pthread_mutex_getprioceilingpthread_mutex_setprioceiling 子例程动态控制互斥对象的 prioceiling 属性。 动态更改互斥对象的优先级 最高限度时,该互斥对象由库锁定;互斥对象不能由调用 pthread_mutex_setprioceiling 子例程的线程所有以避免死锁。 增加线程的优先级时动态设置互斥对象的优先级最高限度是十分有用的。

互斥协议的实现是可选的。 每个协议都是一个 POSIX 选项。

继承或保护

两个协议是相似的,它们都会提高拥有互斥对象的线程的优先级。 如果两个协议都可用,那么程序员必须选择一个协议。 选择哪一个协议取决于创建互斥对象的程序员是否可以使用要锁定互斥对象的线程的优先级。 通常情况下,由库定义并且由应用程序线程使用的互斥对象将使用继承协议,而在应用程序内部创建的互斥对象将使用保护协议。

在注重性能的程序中,对性能的考虑也可能影响选择。 在大多数实现中,尤其是在AIX® 中,更改线程的优先级会导致系统调用。 因此,这两个互斥协议生成的系统调用的数量不同,如下:
  • 使用继承协议时,每次尝试锁定互斥对象时如果线程是锁定的则会进行一次系统调用。
  • 使用保护协议时,每次线程锁定互斥对象时始终会进行一次系统调用。

在大多数注重性能的程序中,继承协议应该是可以选择的,因为互斥对象是低争用的对象。 互斥对象很长时间 没有被谁所拥有;所以,线程试图锁定它们的时候不可能被阻塞。