C++ 多线程并发控制——互斥锁 pthread_mutex

本文深入探讨了C++多线程编程中互斥锁的概念及其使用方法,包括初始化、锁定、解锁等操作,以及如何避免饥饿和死锁状态,通过实例展示了互斥锁在多线程环境下的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题描述:有两个线程,主线程负责接收数据,并暂时保存在内存中,当内存中数量达到一定数据量时,批量提交到oracle中;另一个线程作为提交线程,定时检查一遍,不论内存中数据量达到多少,定期将数据提交到oracle中。两个线程并发进行,第一个写入内存或者数据库的时候,提交线程需要挂起,反之,主线程也需要被挂起。于是,特意来了解一下C++多线程中互斥锁的概念,简单的应用一下。

--------------------------------------------------------------------------------------------------------------------------------

这篇文章写的简单易懂,值得新手学习:http://blog.chinaunix.net/uid-21411227-id-1826888.html

另外,还有一篇文章提供了一个封装好的互斥锁程序,学习一下:http://blog.youkuaiyun.com/chexlong/article/details/7058283


1.引言:

互斥锁,是一种信号量,常用来防止两个进程或线程在同一时刻访问相同的共享资源。可以保证以下三点:

原子性:把一个互斥量锁定为一个原子操作,这意味着操作系统(或pthread函数库)保证了如果一个线程锁定了一个互斥量,没有其他线程在同一时间可以成功锁定这个互斥量。

唯一性:如果一个线程锁定了一个互斥量,在它解除锁定之前,没有其他线程可以锁定这个互斥量。

非繁忙等待:如果一个线程已经锁定了一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何cpu资源),直到第一个线程解除对这个互斥量的锁定为止,第二个线程则被唤醒并继续执行,同时锁定这个互斥量。

从以上三点,我们看出可以用互斥量来保证对变量(关键的代码段)的排他性访问。

2.函数说明:

需要的头文件:pthread.h


1
)初始化互斥锁

函数原型:int  pthread_mutex_init(pthread_mutex_t *mp, const pthread_mutexattr_t *mattr)

参数说明:mp 互斥锁地址    mattr  属性 通常默认 null

初始化互斥锁之前,必须将其所在的内存清零。

如果互斥锁已初始化,则它会处于未锁定状态。互斥锁可以位于进程之间共享的内存中或者某个进程的专用内存中。


2)锁定互斥锁

函数原型:

int pthread_mutex_lock(pthread_mutex_t *mutex); #include <pthread.h> pthread_mutex_t mutex; int ret; ret = pthread_ mutex_lock(&mp); /* acquire the mutex */

函数说明:

 pthread_mutex_lock() 返回时,该互斥锁已被锁定。调用线程是该互斥锁的属主。如果该互斥锁已被另一个线程锁定和拥有,则调用线程将阻塞,直到该互斥锁变为可用为止

如果互斥锁类型为 PTHREAD_MUTEX_NORMAL则不提供死锁检测。尝试重新锁定互斥锁会导致死锁。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或未锁定,则将产生不确定的行为。

如果互斥锁类型为 PTHREAD_MUTEX_ERRORCHECK则会提供错误检查。如果某个线程尝试重新锁定的互斥锁已经由该线程锁定,则将返回错误。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或者未锁定,则将返回错误。

如果互斥锁类型为 PTHREAD_MUTEX_RECURSIVE则该互斥锁会保留锁定计数这一概念。线程首次成功获取互斥锁时,锁定计数会设置为 1。线程每重新锁定该互斥锁一次,锁定计数就增加 1。线程每解除锁定该互斥锁一次,锁定计数就减小 1 锁定计数达到 0 时,该互斥锁即可供其他线程获取。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或者未锁定,则将返回错误。

如果互斥锁类型是 PTHREAD_MUTEX_DEFAULT则尝试以递归方式锁定该互斥锁将产生不确定的行为。对于不是由调用线程锁定的互斥锁,如果尝试解除对它的锁定,则会产生不确定的行为。如果尝试解除锁定尚未锁定的互斥锁,则会产生不确定的行为。

返回值:

pthread_mutex_lock() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。

EAGAIN:由于已超出了互斥锁递归锁定的最大次数,因此无法获取该互斥锁。

EDEADLK:当前线程已经拥有互斥锁。


3)解除锁定互斥锁

函数原型:

int pthread_mutex_unlock(pthread_mutex_t *mutex); #include <pthread.h> pthread_mutex_t mutex; int ret; ret = pthread_mutex_unlock(&mutex); /* release the mutex */

函数说明:pthread_mutex_unlock() 可释放 mutex 引用的互斥锁对象。互斥锁的释放方式取决于互斥锁的类型属性。如果调用pthread_mutex_unlock() 时有多个线程被 mutex 对象阻塞,则互斥锁变为可用时调度策略可确定获取该互斥锁的线程。对于PTHREAD_MUTEX_RECURSIVE 类型的互斥锁,当计数达到零并且调用线程不再对该互斥锁进行任何锁定时,该互斥锁将变为可用。

返回值:pthread_mutex_unlock() 在成功完成之后会返回零。

其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。

EPERM :当前线程不拥有互斥锁。

   

4)使用非阻塞互斥锁锁定

函数原型:

int pthread_mutex_trylock(pthread_mutex_t *mutex); #include <pthread.h> pthread_mutex_t mutex; int ret; ret = pthread_mutex_trylock(&mutex); /* try to lock the mutex */

函数说明:pthread_mutex_trylock()  pthread_mutex_lock() 的非阻塞版本。如果 mutex 所引用的互斥对象当前被任何线程(包括当前线程)锁定,则将立即返回该调用。否则,该互斥锁将处于锁定状态,调用线程是其属主。

返回值:pthread_mutex_trylock() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。

EBUSY :

由于 mutex 所指向的互斥锁已锁定,因此无法获取该互斥锁。

EAGAIN:描述:

由于已超出了 mutex 的递归锁定最大次数,因此无法获取该互斥锁。

5)销毁互斥锁

函数原型:

int pthread_mutex_destroy(pthread_mutex_t *mp); #include <pthread.h> pthread_mutex_t mp; int ret; ret = pthread_mutex_destroy(&mp); /* mutex is destroyed */请注意,没有释放用来存储互斥锁的空间。

返回值:

pthread_mutex_destroy() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。

EINVAL: mp 指定的值不会引用已初始化的互斥锁对象。


3.例子:

互斥锁用来保证一段时间内只有一个线程在执行一段代码。必要性显而易见:假设各个线程向同一个文件顺序写入数据,最后得到的结果一定是灾难性的。

我们先看下面一段代码。这是一个读/写程序,它们公用一个缓冲区,并且我们假定一个缓冲区只能保存一条信息。即缓冲区只有两个状态:有信息或没有信息。

void reader_function ( void );

void writer_function ( void );

char buffer;

int buffer_has_item=0;

pthread_mutex_t mutex;

struct timespec delay;

void main ( void ){

 pthread_t reader;

 /* 定义延迟时间*/

 delay.tv_sec = 2;

 delay.tv_nec = 0;

 /* 用默认属性初始化一个互斥锁对象*/

 pthread_mutex_init (&mutex,NULL);

pthread_create(&reader, pthread_attr_default, (void *)&reader_function), NULL);

 writer_function( );

}

void writer_function (void){

 while(1){

  /* 锁定互斥锁*/

  pthread_mutex_lock (&mutex);

  if (buffer_has_item==0){

   buffer=make_new_item( );

   buffer_has_item=1;

  }

  /* 打开互斥锁*/

  pthread_mutex_unlock(&mutex);

  pthread_delay_np(&delay);

 }

}

void reader_function(void){

 while(1){

  pthread_mutex_lock(&mutex);

  if(buffer_has_item==1){

   consume_item(buffer);

   buffer_has_item=0;

  }

  pthread_mutex_unlock(&mutex);

  pthread_delay_np(&delay);

 }

}

程序说明:

这里声明了互斥锁变量mutex,结构pthread_mutex_t为不公开的数据类型,其中包含一个系统分配的属性对象。函数pthread_mutex_init用来生成一个互斥锁。NULL参数表明使用默认属性。如果需要声明特定属性的互斥锁,须调用函数pthread_mutexattr_init。函数pthread_mutexattr_setpshared和函数pthread_mutexattr_settype用来设置互斥锁属性。前一个函数设置属性pshared,它有两个取值,PTHREAD_PROCESS_PRIVATEPTHREAD_PROCESS_SHARED。前者用来不同进程中的线程同步,后者用于同步本进程的不同线程。

在上面的例子中,我们使用的是默认属性PTHREAD_PROCESS_ PRIVATE。后者用来设置互斥锁类型,可选的类型有PTHREAD_MUTEX_NORMALPTHREAD_MUTEX_ERRORCHECKPTHREAD_MUTEX_RECURSIVEPTHREAD _MUTEX_DEFAULT。它们分别定义了不同的上锁、解锁机制,一般情况下,选用最后一个默认属性。

pthread_mutex_lock声明开始用互斥锁上锁,此后的代码直至调用pthread_mutex_unlock为止,均被上锁,即同一时间只能被一个线程调用执行。当一个线程执行到pthread_mutex_lock处时,如果该锁此时被另一个线程使用,那此线程被阻塞,即程序将等待到另一个线程释放此互斥锁。在上面的例子中,我们使用了pthread_delay_np函数,让线程睡眠一段时间,就是为了防止一个线程始终占据此函数。

4.饥饿和死锁的情形

当一个互斥量已经被别的线程锁定后,另一个线程调用pthread_mutex_lock()函数去锁定它时,会挂起自己的线程等待这个互斥量被解锁。可能出现以下两种情况:

饥饿状态”:这个互斥量一直没有被解锁,等待锁定它的线程将一直被挂着,即它请求某个资源,但永远得不到它。用户必须在程序中努力避免这种“饥饿”状态出现。Pthread函数库不会自动处理这种情况。

死锁”:一组线程中的所有线程都在等待被同组中另外一些线程占用的资源,这时,所有线程都因等待互斥量而被挂起,它们中的任何一个都不可能恢复运行,程序无法继续运行下去。这时就产生了死锁。Pthread函数库可以跟踪这种情形,最后一个线程试图调用pthread_mutex_lock()时会失败,并返回类型为EDEADLK的错误。



<think>我们正在寻找Windows平台上替代pthread_mutex_t的C++类或机制。根据引用[1],pthread_mutex_t是POSIX线程库中的互斥锁,用于多线程同步。在Windows平台上,我们可以使用Windows API提供的同步原语,或者使用C++标准库中的互斥量。 在Windows C++中,有以下几种选择: 1. Windows API提供的互斥对象(如`HANDLE`类型的互斥体,使用`CreateMutex`、`WaitForSingleObject`、`ReleaseMutex`等函数)。 2. C++11标准库中的`std::mutex`(以及相关的锁管理类如`std::lock_guard`、`std::unique_lock`)等,这些是跨平台的,在Windows上也可以使用。 3. Microsoft的并发运行时库(Concurrency Runtime)中的同步原语,如`concurrency::critical_section`(但注意,这个只适用于Microsoft的并发运行时,且通常与PPL任务一起使用)。 考虑到可移植性和现代C++编程实践,推荐使用C++11标准库中的`std::mutex`。 下面我们分别简要介绍: ### 1. 使用C++11标准库的`std::mutex` C++11标准库提供了`<mutex>`头文件,其中定义了`std::mutex`,它可以直接替代pthread_mutex_t。 示例代码: ```cpp #include <iostream> #include <thread> #include <mutex> std::mutex mtx; // 全局互斥量 void print_thread_id(int id) { // 使用std::lock_guard在作用域内自动加锁和解锁 std::lock_guard<std::mutex> lock(mtx); std::cout << "Thread #" << id << '\n'; } int main() { std::thread threads[10]; for (int i = 0; i < 10; ++i) { threads[i] = std::thread(print_thread_id, i + 1); } for (auto& th : threads) { th.join(); } return 0; } ``` 在这个例子中,`std::mutex`用于保护对标准输出的访问,确保每次只有一个线程可以输出。 ### 2. 使用Windows API的互斥体 如果你需要更底层的控制或者需要跨进程的互斥体,可以使用Windows API的互斥体。但是,这种方式不具备可移植性。 示例代码: ```cpp #include <windows.h> #include <iostream> #include <vector> HANDLE hMutex; DWORD WINAPI ThreadFunc(LPVOID lpParam) { DWORD dwWaitResult = WaitForSingleObject(hMutex, INFINITE); if (dwWaitResult == WAIT_OBJECT_0) { // 访问共享资源 std::cout << "Thread " << GetCurrentThreadId() << " is working\n"; // 模拟工作 Sleep(1000); ReleaseMutex(hMutex); } return 0; } int main() { hMutex = CreateMutex(NULL, FALSE, NULL); if (hMutex == NULL) { std::cerr << "CreateMutex error: " << GetLastError() << '\n'; return 1; } const int numThreads = 5; HANDLE threads[numThreads]; for (int i = 0; i < numThreads; ++i) { threads[i] = CreateThread(NULL, 0, ThreadFunc, NULL, 0, NULL); if (threads[i] == NULL) { std::cerr << "CreateThread error: " << GetLastError() << '\n'; return 1; } } WaitForMultipleObjects(numThreads, threads, TRUE, INFINITE); for (int i = 0; i < numThreads; ++i) { CloseHandle(threads[i]); } CloseHandle(hMutex); return 0; } ``` ### 比较 - `std::mutex`是C++标准的一部分,跨平台,使用简单,推荐在新项目中使用。 - Windows API互斥体功能更强大(例如可以跨进程,可以设置超时等),但仅适用于Windows平台。 因此,对于大多数情况,使用`std::mutex`是最佳选择。 ### 相关问题 1. 如何在C++中使用`std::mutex`实现递归锁? 2. Windows API中的互斥体和临界区(Critical Section)有什么区别? 3. C++标准库中的互斥量有哪些类型(例如`std::mutex`, `std::recursive_mutex`等)?它们分别适用于什么场景?</think>在Windows平台上,C++开发者可以使用以下替代`pthread_mutex_t`的机制: ### 一、推荐方案:C++标准库 `<mutex>` C++11及以上版本提供了跨平台的互斥量实现,可直接替代`pthread_mutex_t`: ```cpp #include <mutex> std::mutex mtx; // 声明互斥量 void thread_function() { std::lock_guard<std::mutex> lock(mtx); // 自动加锁/解锁 // 访问共享资源 } ``` **优势**: 1. 跨平台兼容(Windows/Linux/macOS) 2. 符合现代C++标准(RAII机制) 3. 无需手动管理锁生命周期 ### 二、Windows原生API方案 若需使用Windows特有功能(如跨进程同步),可选用: ```cpp #include <windows.h> HANDLE hMutex = CreateMutex(NULL, FALSE, NULL); // 创建互斥体 void thread_func() { WaitForSingleObject(hMutex, INFINITE); // 加锁 // 访问共享资源 ReleaseMutex(hMutex); // 解锁 } // 程序退出时需调用 CloseHandle(hMutex); ``` **适用场景**: 1. 需要跨进程同步 2. 需要超时控制(通过`WaitForSingleObject`第二个参数) 3. 需要高级特性(如安全描述符) ### 三、其他替代方案 1. **临界区(Critical Section)**: ```cpp CRITICAL_SECTION cs; InitializeCriticalSection(&cs); EnterCriticalSection(&cs); // 临界区代码 LeaveCriticalSection(&cs); DeleteCriticalSection(&cs); ``` **特点**:轻量级,仅限进程内线程同步(比互斥体快5-10倍)[^1]。 2. **Slim读写锁(SRW Lock)**: ```cpp SRWLOCK srwLock; InitializeSRWLock(&srwLock); AcquireSRWLockExclusive(&srwLock); // 写锁 ReleaseSRWLockExclusive(&srwLock); ``` **特点**:Vista+系统支持,高性能读写锁[^2]。 --- ### 方案对比表 | **特性** | `std::mutex` (C++11) | Windows 互斥体 | 临界区 (CRITICAL_SECTION) | |------------------------|----------------------|---------------------|--------------------------| | 跨平台 | ✅ | ❌ (仅Windows) | ❌ (仅Windows) | | 跨进程同步 | ❌ | ✅ | ❌ | | 性能 | 高 | 中 | **极高** | | 死锁检测 | ❌ | ✅(WaitChain遍历) | ❌ | | 超时等待 | ❌(需`std::timed_mutex`)| ✅ | ❌ | --- ### 迁移注意事项 1. **递归锁**: POSIX的`PTHREAD_MUTEX_RECURSIVE`对应Windows的`CreateMutex`或`std::recursive_mutex` 2. **条件变量**: 使用`std::condition_variable`替代`pthread_cond_t`: ```cpp std::mutex mtx; std::condition_variable cv; // 等待端 std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, []{ return condition; }); // 通知端 cv.notify_one(); ``` 3. **错误处理**: Windows API错误需用`GetLastError()`获取,而C++标准库抛出`std::system_error`异常。 > 📌 **建议**:优先使用C++标准库(`std::mutex`),除非需要Windows特有功能。迁移时注意锁粒度和死锁预防[^3][^4]。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值