`lock()` 和 `unlock()` 线程同步函数

1) 函数的概念与用途

lock()unlock() 不是特定的标准库函数,而是线程同步原语的一般概念 ,用于在多线程环境中保护共享资源。在不同的编程环境和库中,这些函数有不同的具体实现(如 POSIX 线程的 pthread_mutex_lock() 或 C++ 的 std::mutex::lock())。

可以将 lock()unlock() 想象成"资源门的钥匙":当一个线程需要访问共享资源时,它必须先获取锁(拿到钥匙),使用完资源后释放锁(归还钥匙)。这样可以确保同一时间只有一个线程能访问受保护的资源,防止数据竞争和不一致。

典型应用场景包括:

  • 共享数据保护:保护多线程共享的变量、数据结构
  • 临界区控制:确保代码关键部分只能由一个线程执行
  • 资源访问序列化:对有限资源(如文件、网络连接)的有序访问
  • 生产者-消费者模式:协调生产者和消费者线程之间的数据交换

2) 函数的声明与出处

锁的实现因平台和编程语言而异,以下是几种常见实现:

POSIX 线程 (pthread)

c 复制代码
#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

C++ 标准库

cpp 复制代码
#include <mutex>

std::mutex mtx;
mtx.lock();    // 获取锁
mtx.unlock();  // 释放锁

Windows API

c 复制代码
#include <windows.h>

CRITICAL_SECTION cs;
EnterCriticalSection(&cs);   // 获取锁
LeaveCriticalSection(&cs);   // 释放锁

3) 参数详解:互斥锁对象

对于 POSIX 线程的 pthread_mutex_lock/unlock

  • pthread_mutex_t *mutex
    • 作用:指向要锁定/解锁的互斥锁对象的指针
    • 要求 :互斥锁必须已通过 pthread_mutex_init() 初始化
    • 注意:不同类型的互斥锁有不同的特性(普通、递归、错误检查等)

4) 返回值:操作状态

对于 POSIX 线程的 pthread_mutex_lock/unlock

  • 返回值类型int
  • 返回值含义
    • 成功:返回 0
    • 失败 :返回错误码(如 EINVAL 无效参数,EDEADLK 死锁等)

5) 实战演示:多种使用场景

示例 1:POSIX 线程保护共享变量

c 复制代码
#include <stdio.h>
#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int shared_counter = 0;

void* thread_function(void* arg) {
    for (int i = 0; i < 100000; i++) {
        // 获取锁
        pthread_mutex_lock(&mutex);
        
        // 临界区开始
        shared_counter++;
        // 临界区结束
        
        // 释放锁
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

int main() {
    pthread_t thread1, thread2;
    
    pthread_create(&thread1, NULL, thread_function, NULL);
    pthread_create(&thread2, NULL, thread_function, NULL);
    
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    
    printf("Final counter value: %d (expected: 200000)\n", shared_counter);
    
    pthread_mutex_destroy(&mutex);
    return 0;
}

示例 2:C++ 使用 std::mutex

cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>

std::mutex mtx;
int shared_value = 0;

void increment() {
    for (int i = 0; i < 100000; i++) {
        mtx.lock();        // 获取锁
        shared_value++;    // 临界区
        mtx.unlock();      // 释放锁
    }
}

int main() {
    std::vector<std::thread> threads;
    
    for (int i = 0; i < 10; i++) {
        threads.emplace_back(increment);
    }
    
    for (auto& t : threads) {
        t.join();
    }
    
    std::cout << "Final value: " << shared_value << " (expected: 1000000)" << std::endl;
    return 0;
}

示例 3:使用 RAII 避免忘记解锁 (C++)

cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>

std::mutex mtx;
int shared_value = 0;

void increment() {
    for (int i = 0; i < 100000; i++) {
        // 使用 std::lock_guard 自动管理锁生命周期
        std::lock_guard<std::mutex> lock(mtx);
        shared_value++; // 临界区
        // lock 析构时自动释放锁
    }
}

int main() {
    std::vector<std::thread> threads;
    
    for (int i = 0; i < 10; i++) {
        threads.emplace_back(increment);
    }
    
    for (auto& t : threads) {
        t.join();
    }
    
    std::cout << "Final value: " << shared_value << " (expected: 1000000)" << std::endl;
    return 0;
}

6) 编译方式与注意事项

编译 POSIX 线程程序:

bash 复制代码
gcc -o lock_demo lock_demo.c -lpthread

编译 C++ 程序:

bash 复制代码
g++ -o lock_demo lock_demo.cpp -std=c++11 -lpthread

关键注意事项:

  1. 死锁风险:不正确使用锁可能导致死锁(多个线程互相等待对方释放锁)
  2. 性能影响:过度使用锁会降低程序性能,应尽量减少锁的持有时间
  3. 锁粒度:选择适当的锁粒度(粗粒度减少开销但降低并发性,细粒度提高并发性但增加开销)
  4. 异常安全 :在 C++ 中使用 RAII 模式(如 std::lock_guard)确保异常时锁能被正确释放
  5. 锁类型选择:根据需求选择合适的锁类型(普通锁、递归锁、读写锁等)

7) 执行结果说明

示例 1 输出:

复制代码
Final counter value: 200000 (expected: 200000)

展示了如何使用互斥锁保护共享计数器,确保两个线程各增加 100,000 次后结果为 200,000。

示例 2 输出:

复制代码
Final value: 1000000 (expected: 1000000)

显示了 C++ 中使用 std::mutex 保护共享变量,10 个线程各增加 100,000 次后结果为 1,000,000。

示例 3 输出:

复制代码
Final value: 1000000 (expected: 1000000)

演示了使用 RAII 模式(std::lock_guard)自动管理锁的生命周期,避免忘记解锁。

8) 总结:锁的工作流程与价值

锁的工作流程可以总结如下:
是 否 线程尝试获取锁 lock() 锁是否可用? 获取锁, 进入临界区 线程阻塞等待 执行临界区代码
访问共享资源 释放锁 unlock() 其他线程可以竞争锁

锁是多线程编程中的核心同步机制,它的价值在于:

  1. 数据一致性:防止多个线程同时修改共享数据导致的不一致
  2. 执行有序性:确保临界区代码的原子性执行
  3. 线程协调:协调多个线程对有限资源的访问

多线程同步需求 如何选择同步机制? 保护共享数据 使用互斥锁 Mutex 读写分离场景 使用读写锁 R/W Lock 线程间通知 使用条件变量 Condition Variable 一次性初始化 使用 once_flag

最佳实践建议:

  1. 最小化临界区:尽量减少锁的持有时间,只保护真正需要同步的代码
  2. 避免嵌套锁:尽量避免在持有锁时获取其他锁,防止死锁
  3. 使用RAII模式 :在C++中使用std::lock_guardstd::unique_lock自动管理锁
  4. 锁顺序一致:如果必须使用多个锁,确保所有线程以相同顺序获取它们
  5. 考虑替代方案:根据场景考虑使用无锁编程、原子操作或其他同步机制

lock()unlock() 是多线程编程的基础工具,正确使用它们对于编写线程安全、高效并发的程序至关重要。掌握锁的原理、使用方法和注意事项,可以帮助开发者避免常见的多线程问题,如数据竞争、死锁和性能瓶颈。

相关推荐
zhanghongyi_cpp14 分钟前
linux的conda配置与应用阶段的简单指令备注
linux·python·conda
Cuit小唐1 小时前
VsCode使用SFTP连接Linux
linux·ide·vscode
小xin过拟合1 小时前
day20 二叉树part7
开发语言·数据结构·c++·笔记·算法
EstrangedZ1 小时前
vscode(MSVC)进行c++开发的时,在debug时查看一个eigen数组内部的数值
c++·ide·vscode
乌萨奇也要立志学C++2 小时前
【C++详解】哈希表概念与实现 开放定址法和链地址法、处理哈希冲突、哈希函数介绍
c++·哈希算法·散列表
Forward♞3 小时前
Qt——网络通信(UDP/TCP/HTTP)
开发语言·c++·qt
2401_858286113 小时前
OS26.【Linux】进程程序替换(下)
linux·运维·服务器·开发语言·算法·exec·进程
重启的码农4 小时前
Windows虚拟显示器MttVDD源码分析 (3) 驱动回调与入口点 (WDF/IddCx Callbacks)
c++·windows·操作系统
重启的码农4 小时前
Windows虚拟显示器MttVDD源码分析 (4) 间接设备上下文 (IndirectDeviceContext)
c++·windows·操作系统