C++ / a working model

132 / 163   ·   C11   ·   约 8 分钟

先记住这句话

锁允许程序员保护临界区,确保共享数据的更新以原子方式发生,从而避免并发线程之间的竞态条件。

本篇内容
  1. 用锁保护共享更新
  2. 细粒度与粗粒度锁定
  3. 锁实现中的硬件与操作系统支持
  4. 中断屏蔽方法的局限
  5. 运行示例
  6. 动手练习

官方章节 PDF

用锁保护共享更新

程序员在敏感代码前后调用锁获取和释放。这确保封闭操作对其他线程呈现为原子。锁变量记录其状态是空闲还是被单个线程持有。

细粒度与粗粒度锁定

单一全局锁会强制所有临界区串行执行。为不同数据结构使用独立锁可以提高并行度,让无关操作同时进行。

锁实现中的硬件与操作系统支持

高效锁依赖处理器提供的原子读写指令,以及操作系统在锁被占用时让线程休眠而非忙等的能力。

中断屏蔽方法的局限

关闭中断仅适用于单处理器,且赋予用户代码过多特权,恶意或错误程序可能使系统无法响应硬件事件。

常见误区

  • 忘记解锁会使等待线程永远阻塞。
  • 持锁期间执行可能睡眠的操作容易导致死锁。

运行一个例子

最低标准 C11 · 完整程序 · 下载 .c

#include <stdio.h>
#include <pthread.h>

static int shared_counter = 0;
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

static void *worker(void *arg) { (void)arg;
    (void)arg;
    for (int i = 0; i < 5000; ++i) {
        pthread_mutex_lock(&mtx);
        ++shared_counter;
        pthread_mutex_unlock(&mtx);
    }
    return NULL;
}

int main(void) {
    pthread_t t1, t2;
    pthread_create(&t1, NULL, worker, NULL);
    pthread_create(&t2, NULL, worker, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    printf("Shared counter is %d\n", shared_counter);
    return 0;
}

在本地编译

gcc -std=c11 -Wall -Wextra -Wpedantic -Werror -pthread ostep-28-locks.c -o example && ./example

预期结果

Shared counter is 10000

CHECK YOUR UNDERSTANDING

合上答案,试着解释。

为何仅用普通变量作为标志的锁无法保证互斥?

查看参考答案

测试标志是否空闲与随后将其设为占用这两步可能被调度打断,导致多个线程同时进入临界区。

继续查证

标准草案与官方章节会更新;版本标记只说明示例最低要求。

回到目录