线程的互斥是多线程编程中的一个关键概念,旨在确保多个线程在访问共享资源时不会发生数据竞争或其他一致性问题。让我们详细探讨一下这个概念及其实现方式。
大多数情况下,线程使用的数据是局部变量,存储在线程的栈空间内,这些变量仅属于单个线程,其他线程无法访问。然而,某些变量需要在线程间共享,这些称为共享变量,通过共享变量,线程之间可以进行交互。多个线程并发操作共享变量时,可能会引发问题,因此需要互斥来确保数据的一致性。
让我们通过一个实际的例子——抢票系统——来理解这个问题:
// 操作共享变量会有问题的售票系统代码 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> int ticket = 100; void *route(void *arg) { char *id = (char*)arg; while (1) { if (ticket > 0) { usleep(1000); printf("%s sells ticket:%d\n", id, ticket); ticket--; } else { break; } } } int main(void) { pthread_t t1, t2, t3, t4; pthread_create(&t1, NULL, route, "thread 1"); pthread_create(&t2, NULL, route, "thread 2"); pthread_create(&t3, NULL, route, "thread 3"); pthread_create(&t4, NULL, route, "thread 4"); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL); pthread_join(t4, NULL); }
在没有加锁(互斥)的代码执行中,我们发现票数竟然变成了负数,这是不可接受的。
这是因为共享资源在访问时没有被保护,且操作本身不是原子的:
-- 操作对应三条汇编指令:
为了解决这个问题,需要确保三点:
这三点可以通过使用互斥锁(mutex)来实现。
这种锁定义在全局代码段,不需要销毁。
这种锁需要在局部代码段定义和初始化,并且需要手动销毁。
以上两种锁的使用需要在指定加锁区域进行加锁和解锁。
C++ 注重 RAII 编程思想,可以将锁封装成 RAII 风格的锁。
我们可以将锁封装成 LockGuard 类,构造函数加锁,析构函数解锁,这样可以创建局部对象,让编译器自动调用构造和析构函数,无需手动加锁和解锁。
#ifndef __LOCK_GUARD_HPP__ #define __LOCK_GUARD_HPP__ #include <iostream> #include <pthread.h> class LockGuard { public: LockGuard(pthread_mutex_t *mutex) : _mutex(mutex) { pthread_mutex_lock(_mutex); // 构造加锁 } ~LockGuard() { pthread_mutex_unlock(_mutex); // 析构解锁 } private: pthread_mutex_t *_mutex; }; #endif
在学习了加锁方式后,我们可以优化抢票系统:
void route(ThreadData *td) { while (true) { { LockGuard guard(&td->_mutex); // 临时对象,RAII风格的加锁和解锁 if (td->_tickets > 0) { usleep(1000); printf("%s running, get tickets: %d\n", td->_name.c_str(), td->_tickets); td->_tickets--; td->_total++; } else { break; } } } }
加锁后,票数不再变成负数,问题得到解决。
通过上面的例子,我们意识到简单的 i++ 或 ++i 操作不是原子的,可能导致数据一致性问题。为了实现互斥锁操作,大多数体系结构提供了 swap 或 exchange 指令,这些指令将寄存器和内存单元的数据进行交换,确保原子性,即使在多处理器平台上,访问内存的总线周期也有先后顺序,一个处理器上的交换指令执行时,另一个处理器的交换指令只能等待。
所有线程在争锁时,只有一个锁,交换过程只有一条汇编指令,因此是原子的。
CPU 寄存器硬件只有一套,但 CPU 寄存器内部的数据(线程的硬件上下文)有多套。数据在内存中时,所有线程都能访问,属于共享的,但一旦转移到 CPU 内部寄存器,就属于单个线程私有的。
以上就是【Linux】多线程安全之道:互斥、加锁技术与底层原理的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号