免费网站流量统计工具做网站多少钱zwnet
- 作者: 五速梦信息网
- 时间: 2026年03月21日 10:23
当前位置: 首页 > news >正文
免费网站流量统计工具,做网站多少钱zwnet,济南城市建设职业学院官网招生网,做网站还是做公众号文章目录 引言#xff1a;一、线程互斥1.1 进程线程间的互斥相关背景概念1.2 多线程可能出现的问题情况一#xff1a;使用过期值情况二#xff1a;重新读取值 1.3 mutex1.3.1 基本概念1.3.2 std::mutex1.3.3 锁的管理类1.3.4 常见问题与解决方案1.3.5 使用场景1.3.6 示例代码… 文章目录 引言一、线程互斥1.1 进程线程间的互斥相关背景概念1.2 多线程可能出现的问题情况一使用过期值情况二重新读取值 1.3 mutex1.3.1 基本概念1.3.2 std::mutex1.3.3 锁的管理类1.3.4 常见问题与解决方案1.3.5 使用场景1.3.6 示例代码 二、线程同步2.1 为什么需要线程同步2.2 条件变量2.3 同步概念与竞态条件2.4 条件变量函数2.4.1 初始化条件变量2.4.2 销毁条件变量2.4.3 等待条件阻塞2.4.4 定时等待2.4.5 唤醒单个线程2.4.6 唤醒所有线程2.4.7 条件变量使用规范 2.5 生产者消费者模型2.5.1 模型概述2.5.2 关键组件解析2.5.3 模型工作原理2.5.4 模型优点2.5.5 基于阻塞队列的生产者消费者模型代码示例 2.6 为什么 pthread_cond_wait 需要互斥量 三、线程安全和重入问题3.1 概念 引言 在现代软件开发中多线程编程已成为提升系统性能和响应能力的必备技能。从服务器后端的高并发请求处理到客户端应用的 UI 与逻辑异步解耦线程间的协同与竞争始终是绕不开的核心命题。然而当多个线程同时访问共享资源时数据竞争、死锁、超卖等问题往往让开发者束手无策 —— 正如经典的 “抢票场景” 中ticket–这一看似简单的操作在汇编层面竟被拆解为三步非原子操作最终导致重复票号与负数票额的诡异现象。 本文将从底层原理出发系统剖析线程互斥与同步机制的实现逻辑通过互斥锁Mutex保护临界区的原子性操作借助条件变量Condition Variable实现生产者与消费者的高效协同最终构建出支持多线程并发的阻塞队列模型。同时我们将深入探讨线程安全与可重入性的本质区别 —— 为何加锁的函数是线程安全的却可能因重入导致死锁如何通过 RAII 机制与原子操作避免这些陷阱 无论你是初涉多线程编程的新手还是希望夯实并发基础的开发者本文都将通过 “问题场景→原理分析→代码实战” 的递进式讲解带你掌握从基础互斥到复杂生产者消费者模型的全流程实现最终写出稳定、高效的线程安全代码。 一、线程互斥 1.1 进程线程间的互斥相关背景概念 临界资源多线程执行流共享的资源就叫做临界资源临界区每个线程内部访问临界资源的代码就叫做临界区互斥任何时刻互斥保证有且只有一个执行流进入临界区访问临界资源通常对临界资源起保护作用原子性后面讨论如何实现不会被任何调度机制打断的操作该操作只有两态要么完成要么未完成 1.2 多线程可能出现的问题 大部分情况线程使用的数据都是局部变量变量的地址空间在线程栈空间内这种情况变量归属单个线程其他线程无法获得这种变量。但有时候很多变量都需要在线程间共享这样的变量称为共享变量可以通过数据的共享完成线程之间的交互。多个线程并发的操作共享变量会带来一些问题。 看下面模拟抢票的代码可以看出多线程使用时的一些问题 1 #include stdio.h2 #include stdlib.h3 #include string.h4 #include unistd.h5 #include pthread.h6 7 8 int ticket 100;9 10 void *route(void *arg)11 {12 char id (char)arg;13 while(1) {14 if(ticket 0) {15 usleep(1000);16 printf(%s sells ticket:%d\n, id, ticket);17 ticket–;18 } else {19 break;20 }21 }22 }23 24 25 int main()26 {27 pthread_t t1, t2, t3, t4;28 29 pthread_create(t1, NULL, route, thread 1);30 pthread_create(t2, NULL, route, thread 2);31 pthread_create(t3, NULL, route, thread 3);32 pthread_create(t4, NULL, route, thread 4); 33 34 pthread_join(t1, NULL);35 pthread_join(t2, NULL);36 pthread_join(t3, NULL);37 pthread_join(t4, NULL);38 39 return 0;40 }41 运行结果 可以看到在模拟多线程进行抢票的时候出现了几个人在买完票时抢到了相同票号的情况甚至还有票的数量竟然小于 0 的情况这是很严重的问题。可是为什么会发生这样的情况呢 其实主要问题出现在 ticket– 上这看上去是一条语句但其实当他被转换成汇编代码时最少也是三条语句
C 代码对应: ticket–
movl ticket, %eax # 1. 读取阶段将 ticket 值加载到 eax 寄存器
decl %eax # 2. 修改阶段将 eax 寄存器的值减 1
movl %eax, ticket # 3. 写入阶段将新值写回 ticket 内存地址所以 – 的操作并非原子的
情况一使用过期值
这就能解释为什么会出现多个人抢到相同票号的情况了 假设线程 t1、t2 是同时执行的且 ticket 1线程 t1 读取到了 ticket 1 将其加载到寄存器 eax 中满足条件进入循环此时 t1 被系统中断切换到 t2 t2 读取了 ticket 1将其加载到寄存器 ebx 中 然后它将寄存器中的值减一再将新值写入到 ticket 内存地址处此时 ticket 内存地址处的值为 0而后线程 t1 被恢复虽然 ticket 的值为 0但是寄存器 eax 的值为1在其进行减一和写回到 ticket 的内存地址之后ticket 的值仍为 0所以就会出现抢到了相同票号的情况。
情况二重新读取值
那为什么会出现超卖情况呢 假设线程 t1、t2、t3、t4 同时执行而此时 ticket 1这四个线程都认为自己满足执行条件进入循环中线程 t1、t2 按照上面的方式使得出现了两次 ticket 0但是因为 t4 被长时间中断它会重新读取 ticket 内存地址中的值然后再执行 – 操作然后再将其写回 ticket 的内存地址中这就导致了 ticket 0 的情况。
1.3 mutex
那么如何解决上面说的问题呢加锁
1.3.1 基本概念
互斥锁Mutex一种同步原语确保同一时间只有一个线程可以访问共享资源。临界区Critical Section访问共享资源的代码段需要用互斥锁保护。原子性Atomicity锁操作是原子的确保线程安全。
1.3.2 std::mutex
最基本的互斥锁不支持递归锁定
#include mutexstd::mutex mtx;void sharedResourceAccess() {mtx.lock(); // 加锁// 临界区代码mtx.unlock(); // 解锁
}注意手动调用lock()和unlock()需确保成对出现否则可能导致死锁。推荐使用 RAII 风格的std::lock_guard或std::unique_lock
void safeAccess() {std::lock_guardstd::mutex lock(mtx); // 自动加锁// 临界区代码
} // 作用域结束自动解锁1.3.3 锁的管理类
C 提供了 RAII 风格的锁管理类自动处理锁的生命周期
std::lock_guard 最简单的锁管理器构造时加锁析构时解锁{std::lock_guardstd::mutex lock(mtx);// 临界区
} // 自动解锁std::unique_lock 更灵活的锁管理器支持延迟加锁、转移所有权和条件变量{std::unique_lockstd::mutex lock(mtx, std::defer_lock); // 延迟加锁if (needLock) {lock.lock(); // 手动加锁}// 临界区
} // 自动解锁std::scoped_lock C17 引入用于同时锁定多个互斥锁避免死锁std::mutex mtx1, mtx2;void safeFunction() {std::scoped_lock lock(mtx1, mtx2); // 原子性地锁定多个锁// 临界区
}1.3.4 常见问题与解决方案
死锁Deadlock 多个线程互相等待对方释放锁导致程序冻结。
解决方案
按固定顺序加锁。使用std::lock或std::scoped_lock同时锁定多个锁。
锁粒度Lock Granularity
过粗多个无关操作共用同一锁导致性能下降。过细管理过多锁增加复杂度可能引发死锁。
优化原则
最小化锁的持有时间。对独立资源使用独立锁。
性能问题 互斥锁的竞争可能成为性能瓶颈。
优化方案
对于简单操作优先使用std::atomic无锁编程。对于读多写少的场景使用std::shared_mutex。
1.3.5 使用场景
保护共享数据如全局变量、容器std::vector、std::map等。实现线程安全的类在类内部使用互斥锁封装共享资源的访问。控制并发访问限制同时执行某个操作的线程数量。
1.3.6 示例代码 1 #include cstdio2 #include cstdlib3 #include cstring4 #include unistd.h5 #include pthread.h6 7 8 int ticket 1000;9 pthread_mutex_t lock PTHREAD_MUTEX_INITIALIZER;;10 11 void *route(void *arg)12 {13 char *id (char *)arg;14 while(1)15 {16 pthread_mutex_lock(lock);17 if(ticket 0)18 {19 usleep(1000);20 printf(%s sells ticket: %d\n, id, ticket);21 ticket–;22 pthread_mutex_unlock(lock);23 }24 else25 {26 pthread_mutex_unlock(lock);27 28 break;29 }30 }31 return nullptr;32 } 33 34 35 int main()36 {37 pthread_t t1, t2, t3, t4;38 39 pthread_create(t1, NULL, route, (void *)thread 1);40 pthread_create(t2, NULL, route, (void *)thread 2);41 pthread_create(t3, NULL, route, (void *)thread 3);42 pthread_create(t4, NULL, route, (void *)thread 4);43 44 pthread_join(t1, NULL);45 pthread_join(t2, NULL);46 pthread_join(t3, NULL);47 pthread_join(t4, NULL);48 return 0;49 } 运行结果 这样就不会出现上面那两个问题了
二、线程同步
线程同步是多线程编程中的核心概念用于协调多个线程的执行顺序确保它们正确、有序地访问共享资源防止出现竞态条件race condition和数据不一致等问题。
2.1 为什么需要线程同步
当多个线程同时访问共享资源如全局变量、文件、内存区域等时如果没有适当的同步机制可能会导致
数据竞争Data Race多个线程同时读写同一数据竞态条件Race Condition结果依赖于线程执行的顺序死锁Deadlock线程相互等待对方释放资源资源耗尽无限制地创建线程或等待资源
2.2 条件变量
当一个线程互斥地访问某个变量时它可能发现在其它线程改变状态之前它什么也做不了。例如一个线程访问队列时发现队列为空它只能等待只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。
2.3 同步概念与竞态条件
同步在保证数据安全的前提下让线程能够按照某种特定的顺序访问临界资源从而有效避免饥饿问题叫做同步竞态条件因为时序问题而导致程序异常我们称之为竞态条件。在线程场景下这种问题也不难理解
2.4 条件变量函数
2.4.1 初始化条件变量
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);功能初始化条件变量参数 cond指向条件变量的指针attr条件变量属性通常设为 NULL使用默认属性 返回值成功返回 0失败返回错误码
2.4.2 销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);功能销毁条件变量释放相关资源参数cond指向条件变量的指针返回值成功返回 0失败返回错误码
2.4.3 等待条件阻塞
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);功能在条件变量上等待同时释放关联的互斥锁参数 cond指向条件变量的指针mutex指向关联互斥锁的指针 返回值成功返回 0失败返回错误码行为 原子地解锁互斥锁阻塞当前线程直到被唤醒被唤醒后重新锁定互斥锁返回调用线程
2.4.4 定时等待
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,const struct timespec *abstime);功能带超时的条件等待参数 cond指向条件变量的指针mutex指向关联互斥锁的指针abstime绝对超时时间使用 clock_gettime(CLOCK_REALTIME, …) 获取 返回值 成功返回 0超时返回 ETIMEDOUT失败返回其他错误码
2.4.5 唤醒单个线程
int pthread_cond_signal(pthread_cond_t *cond);功能唤醒至少一个在条件变量上等待的线程参数cond指向条件变量的指针返回值成功返回 0失败返回错误码注意如果有多个线程等待唤醒哪一个取决于调度策略
2.4.6 唤醒所有线程
int pthread_cond_broadcast(pthread_cond_t cond);功能唤醒所有在条件变量上等待的线程参数cond指向条件变量的指针返回值成功返回 0失败返回错误码注意所有等待线程都将被唤醒并竞争互斥锁
2.4.7 条件变量使用规范
等待条件代码
1 pthread_mutex_lock(mutex);
2 while (条件为假)
3 pthread_cond_wait(cond, mutex);
4 // 修改条件
5 pthread_mutex_unlock(mutex);给条件发送信号代码
1 pthread_mutex_lock(mutex);
2 // 设置条件为真
3 pthread_cond_signal(cond);
4 pthread_mutexunlock(mutex);2.5 生产者消费者模型
2.5.1 模型概述
生产者-消费者模型是多线程编程中最经典的并发设计模式用于解决生产者和消费者之间速度不匹配的问题。该模型通过一个共享的缓冲区队列协调生产者和消费者 核心组件
生产者生成数据/任务的线程消费者处理数据/任务的线程缓冲区共享数据结构通常是队列同步机制互斥锁和条件变量
2.5.2 关键组件解析
阻塞队列 (BlockingQueue) 使用互斥锁 (mutex) 保护共享队列使用两个条件变量实现同步 not_empty_消费者等待队列非空not_full_生产者等待队列有空间 支持阻塞操作 (push, pop) 和非阻塞操作 生产者 (Producer) 每个生产者有唯一ID生产固定数量的项目每个项目包含生产者ID和序列号随机延迟模拟生产过程 消费者 (Consumer) 每个消费者有唯一ID持续从队列中取出项目随机延迟模拟消费过程收到特殊值 (-1) 时退出
2.5.3 模型工作原理
正常流程 队列满时 队列空时
2.5.4 模型优点
解耦生产与消费 生产者只需关注数据生成消费者只需关注数据处理双方通过队列交互不直接依赖 平衡负载 缓冲队列平滑生产与消费的速度差异防止生产者过快导致消费者过载防止消费者过快导致生产者空闲 并发控制 支持多生产者多消费者并发工作通过队列自动协调资源分配 流量控制 有限队列大小提供背压机制防止系统过载导致内存耗尽
2.5.5 基于阻塞队列的生产者消费者模型代码示例 1 #pragma once2 3 #include iostream4 #include string5 #include queue6 #include pthread.h7 8 const int defaultcap 5;9 10 template typename T11 class BlockQueue12 {13 private:14 bool IsFull() { return _q.size() _cap; }15 bool IsEmpty() { return _q.empty(); }16 17 public:18 BlockQueue(int cap defaultcap)19 : _cap(cap), _csleep_num(0), _psleep_num(0)20 {21 pthread_mutex_init(_mutex, nullptr);22 pthread_cond_init(_full_cond, nullptr);23 pthread_cond_init(_empty_cond, nullptr);24 }25 void Equeue(const T in)26 {27 pthread_mutex_lock(_mutex);28 // 生产者调用29 while(IsFull())30 {31 _psleep_num;32 std::cout 生产者进入休眠了: _psleep_num _psleep_num std::endl; 33 pthread_cond_wait(_full_cond, _mutex); 34 _psleep_num–;35 } 36 // 100%确定队列有空间37 _q.push(in);38 39 // 临时方案40 // v241 if(_csleep_num 0)42 {43 pthread_cond_signal(_empty_cond);44 std::cout 唤醒消费者… std::endl;45 } 46 47 // pthread_cond_signal(_empty_cond); // 可以48 pthread_mutex_unlock(_mutex); // TODO49 // pthread_cond_signal(_empty_cond); // 可以50 } 51 T Pop()52 {53 // 消费者调用54 pthread_mutex_lock(_mutex);55 while(IsEmpty())56 {57 _csleep_num;58 pthread_cond_wait(_empty_cond, _mutex);59 _csleep_num–;60 }61 T data _q.front();62 _q.pop();63 64 if(_psleep_num 0) 65 {66 pthread_cond_signal(_full_cond);67 std::cout 唤醒消费者 std::endl;68 }69 70 // pthread_cond_signal(_full_cond);71 pthread_mutex_unlock(_mutex);72 return data;73 }74 ~BlockQueue()75 {76 pthread_mutex_destroy(_mutex);77 pthread_cond_destroy(_full_cond);78 pthread_cond_destroy(_empty_cond);79 }80 81 private:82 std::queueT _q; // 临界资源83 int _cap; // 容器大小84 85 pthread_mutex_t _mutex;86 pthread_cond_t _full_cond;87 pthread_cond_t _empty_cond;88 89 int _csleep_num; // 消费者休眠的个数90 int _psleep_num; // 生产者休眠的个数91 };这里只能看出单生产者单消费者无法看出多生产者多消费者但其实在使用上只是在创建生产者消费者线程的数量不同而已。
单生产者单消费者
// 单生产者线程函数
void singleProducer(BlockQueueint bq)
{for (int i 0; i 100; i) {bq-Equeue(i);}
}// 单消费者线程函数
void singleConsumer(BlockQueueint* bq)
{for (int i 0; i 100; i) {bq-Pop();}
}// 主函数
int main()
{BlockQueueint bq;pthread_t producer, consumer;pthread_create(producer, nullptr, singleProducer, bq);pthread_create(consumer, nullptr, singleConsumer, bq);pthread_join(producer, nullptr);pthread_join(consumer, nullptr);return 0;
}多生产者多消费者
// 多生产者线程函数多个线程调用
void multiProducer(BlockQueueint* bq, int id)
{for (int i 0; i 50; i) {bq-Equeue(id * 100 i); // 每个生产者生成不同的数据}
}// 多消费者线程函数多个线程调用
void multiConsumer(BlockQueueint* bq, int id)
{for (int i 0; i 50; i) {int data bq-Pop();std::cout 消费者 id 取出: data std::endl;}
}// 主函数
int main()
{BlockQueueint bq;const int PRODUCERS 3;const int CONSUMERS 2;pthread_t producers[PRODUCERS], consumers[CONSUMERS];// 创建多个生产者线程for (int i 0; i PRODUCERS; i) {pthread_create(producers[i], nullptr, multiProducer, bq);}// 创建多个消费者线程for (int i 0; i CONSUMERS; i) {pthread_create(consumers[i], nullptr, multiConsumer, bq);}// 等待所有线程结束for (int i 0; i PRODUCERS; i) {pthread_join(producers[i], nullptr);}for (int i 0; i CONSUMERS; i) {pthread_join(consumers[i], nullptr);}return 0;
}2.6 为什么 pthread_cond_wait 需要互斥量
条件等待是线程间同步的一种手段如果只有一个线程条件不满足一直等下去都不会满足所以必须要有一个线程通过某些操作改变共享变量使原先不满足的条件变得满足并且友好的通知等待在条件变量上的线程。条件不会无缘无故的突然变得满足了必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。 按照上面的说法我们设计出如下的代码先上锁发现条件不满足解锁然后等待在条件变量上不就行了如下代码
//错误的设计
pthread_mutex_lock(mutex);
while (condition_is_false)
{pthread_mutex_unlock(mutex);//解锁之后等待之前条件可能已经满足信号已经发出但是该信号可能被错过pthread_cond_wait(cond) ;pthread_mutex_lock(mutex) ;pthread_mutex_unlock(mutex);
}由于解锁和等待不是原子操作。调用解锁之后pthread_cond_wait之前如果已经有其他线程获取到互斥量摒弃条件满足发送了信号那么pthread_cond_wait将错过这个信号可能会导致线程永远阻塞在这个pthread_cond_wait。所以解锁和等待必须是一个原子操作。int pthread_cond_wait(pthreadcond t *cond,pthreadmutex t *mutex); 进入该函数后会去看条件量等于0不等于就把互斥量变成1直到cond_wait返回把条件量改成1把互斥量恢复成原样。
三、线程安全和重入问题
3.1 概念
线程安全就是多个线程在访问共享资源时能够正确地执行不会相互干扰或破坏彼此的执行结果。一般而言多个线程并发同一段只有局部变量的代码时不会出现不同的结果。但是对全局变量或者静态变量进行操作并且没有锁保护的情况下容易出现该问题。
重入同一个函数被不同的执行流调用当前一个流程还没有执行完就有其他的执行流再次进入我们称之为重入。一个函数在重入的情况下运行结果不会出现任何不同或者任何问题则该函数被称为可重入函数否则是不可重入函数。
重入其实可以分为两种情况
多线程重入函数信号导致一个执行流重复进入函数 常见的线程不安全的情况 不保护共享变量的函数函数状态随着被调用状态发生变化的函数返回指向静态变量指针的函数调用线程不安全函数的函数 常见的线程安全的情况 每个线程对全局变量或者静态变量只有读取的权限而没有写入的权限一般来说这些线程是安全的类或者接口对于线程来说都是原子操作多个线程之间的切换不会导致该接口的执行结果存在二义性 常见不可重入的情况 调用了 malloc/free 函数因为 malloc 函数是用全局链表来管理堆的调用了标准 I/O 库函数标准 I/O 库的很多实现都以不可重入的方式使用全局数据结构可重入函数体内使用了静态的数据结构 常见可重入的情况 不使用全局变量或静态变量不使用用 malloc 或者 new 开辟出的空间不调用不可重入函数不返回静态或全局数据所有数据都有函数的调用者提供使用本地数据或者通过制作全局数据的本地拷贝来保护全局数据 结论
可重入与线程安全联系 函数是可重入的那就是线程安全的函数是不可重入的那就不能由多个线程使用有可能引发线程安全问题如果一个函数中有全局变量那么这个函数既不是线程安全也不是可重入的。 可重入与线程安全区别 可重入函数是线程安全函数的一种线程安全不一定是可重入的而可重入函数则一定是线程安全的。如果将对临界资源的访问加上锁则这个函数是线程安全的但如果这个重入函数若锁还未释放则会产生死锁因此是不可重入的。 注意 如果不考虑 信号导致一个执行流重复进入函数 这种重入情况线程安全和重入在安全角度不做区分但是线程安全侧重说明线程访问公共资源的安全情况表现的是并发线程的特点可重入描述的是一个函数是否能被重复进入表示的是函数的特点
- 上一篇: 免费网站空间有什么用个人网站制作dw
- 下一篇: 免费网站模板 怎么用河源建筑设计企业名录黄页
相关文章
-
免费网站空间有什么用个人网站制作dw
免费网站空间有什么用个人网站制作dw
- 技术栈
- 2026年03月21日
-
免费网站空间有哪些广州免费建站平台
免费网站空间有哪些广州免费建站平台
- 技术栈
- 2026年03月21日
-
免费网站空间虚拟主机镇江网站搭建
免费网站空间虚拟主机镇江网站搭建
- 技术栈
- 2026年03月21日
-
免费网站模板 怎么用河源建筑设计企业名录黄页
免费网站模板 怎么用河源建筑设计企业名录黄页
- 技术栈
- 2026年03月21日
-
免费网站模板软件html5新增标签有哪些
免费网站模板软件html5新增标签有哪些
- 技术栈
- 2026年03月21日
-
免费网站模板源码域名等于网站网址吗
免费网站模板源码域名等于网站网址吗
- 技术栈
- 2026年03月21日
