南山网站优化,哪些网站可以做网站,网站成功案例怎么做,兰州网站推广排名目录
一、临界资源与线程安全问题
二、互斥#xff1a;让临界区 “独占” 执行
1.互斥锁的原理
2.互斥锁的使用步骤#xff08;pthread 库#xff09;
2.1 定义互斥锁
2.2 初始化互斥锁
2.3 加锁#xff08;进入临界区#xff09;
2.4 解锁#xff08;离开临界区…目录一、临界资源与线程安全问题二、互斥让临界区 “独占” 执行1.互斥锁的原理2.互斥锁的使用步骤pthread 库2.1 定义互斥锁2.2 初始化互斥锁2.3 加锁进入临界区2.4 解锁离开临界区2.5 销毁互斥锁3.互斥锁实战示例三、同步让线程 “按顺序” 执行1. 同步与互斥的关系2.信号量的原理3.信号量的使用步骤3.1 定义信号量3.2 初始化信号量3.3 信号量的 PV 操作3.4 销毁信号量4.信号量同步实战示例四、死锁一、临界资源与线程安全问题临界资源在线程间会被读写操作的资源比如全局变量、文件、硬件设备。线程安全问题多个线程 “穿插执行” 临界资源的操作时会破坏数据一致性。举个例子A看似是一行代码但编译后会分成 3 步读 A→A1→写回 A。如果线程 1 执行到 “读 A” 后被切换到线程 2线程 2 也执行A最终 A 的值会比预期小 —— 这就是数据竞争。二、互斥让临界区 “独占” 执行互斥的核心是排他性访问同一时刻只有一个线程能操作临界资源。1.互斥锁的原理通过 “锁” 来保护临界区代码操作临界资源的代码线程要执行临界区必须先 “加锁”锁被占用时其他线程会阻塞等待线程执行完临界区必须 “解锁”让其他线程可以竞争锁。th1、th2是并发运行的两个线程。也就是代码在运行时th1与th2是穿插进行的。2.互斥锁的使用步骤pthread 库Linux 下用 pthread_mutex_t 实现互斥锁步骤是定义→初始化→加锁→解锁→销毁。2.1 定义互斥锁#include pthread.h // 定义全局/共享的互斥锁 pthread_mutex_t mutex;2.2 初始化互斥锁// 函数原型 int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); // 示例 pthread_mutex_init(mutex, NULL);功能将已经定义好的互斥锁初始化。参数mutex 是要初始化的锁attr 传NULL表示用默认属性。返回值成功返回 0失败返回非 0。2.3 加锁进入临界区// 函数原型 int pthread_mutex_lock(pthread_mutex_t *mutex);功能用指定的互斥锁开始加锁代码成功则进入临界区失败则阻塞等待。注意加锁后的代码是原子操作线程调度不会打断这段代码。2.4 解锁离开临界区// 函数原型 int pthread_mutex_unlock(pthread_mutex_t *mutex);功能将指定的互斥锁解锁让其他线程可以竞争。解锁之后代码不再排他访问。注意加锁和解锁必须成对出现且要在同一个线程中执行。2.5 销毁互斥锁// 函数原型 int pthread_mutex_destroy(pthread_mutex_t *mutex);功能释放互斥锁的资源锁不再使用时调用。3.互斥锁实战示例比如两个线程同时对全局变量 A 做 操作用互斥锁保证线程安全#include pthread.h #include stdio.h int A 0; pthread_mutex_t mutex; // 线程函数 void* th(void* arg) { int i 5000; while (i--) { // 加锁进入临界区 pthread_mutex_lock(mutex); int tmp A; printf(A is %d\n, tmp 1); // 循环输出到 A is 10000 A tmp 1; // 解锁离开临界区 pthread_mutex_unlock(mutex); } return NULL; } int main(int argc, char** argv) { pthread_t tid1, tid2; // 初始化互斥锁 pthread_mutex_init(mutex, NULL); pthread_create(tid1, NULL, th, NULL); pthread_create(tid2, NULL, th, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); // 销毁互斥锁 pthread_mutex_destroy(mutex); return 0; }三、同步让线程 “按顺序” 执行互斥解决了 “资源独占”但没解决 “执行顺序”。同步是让线程按预先约定的顺序执行比如 “线程 1 输出 Hello 后线程 2 再输出 World”。1. 同步与互斥的关系同步是互斥的 “特例”同步不仅要排他访问还要控制执行顺序。实现同步的工具信号量可以理解为 “带计数的锁”。互斥锁加锁和解锁是同一个线程临界区代码短小精悍避免休眠、大耗时的操作信号量th1 释放 th2th2 释放 th1。由线程交叉释放。可以有适当休眠、小的耗时操作2.信号量的原理信号量是一个整数 sem通过 P 操作申请资源和 V 操作释放资源实现同步P 操作sem--若 sem0 则线程阻塞V 操作sem若 sem0 则唤醒一个阻塞的线程。注Linux 下用 sem_t 实现信号量3.信号量的使用步骤步骤是定义→初始化→PV 操作→销毁。3.1 定义信号量#include semaphore.h sem_t sem;3.2 初始化信号量// 函数原型 int sem_init(sem_t *sem, int pshared, unsigned int value);参数sem要初始化的信号量pshared0表示线程间共享非 0 表示进程间共享value信号量初始值比如 0 表示 “无资源”1 表示 “有 1 个资源”。返回值成功返回 0失败返回 -1。3.3 信号量的 PV 操作P 操作申请资源对应sem_wait()int sem_wait(sem_t *sem);功能判断当前 sem 信号量是否有资源可用。如果 sem 有资源 (1)则申请该资源程序继续运行如果 sem 没有资源 (0)则线程阻塞等待一旦有资源则自动申请资源并继续运行程序。V 操作释放资源对应sem_post()int sem_post(sem_t *sem);功能函数可以将指定的 sem 信号量资源释放并默认执行 sem sem1。线程在该函数上不会阻塞。3.4 销毁信号量int sem_destroy(sem_t *sem);功能使用完毕将指定的信号量销毁。4.信号量同步实战示例#include pthread.h #include semaphore.h #include stdio.h #include unistd.h sem_t sem_H, sem_W; // 线程1输出Hello void *th1(void *arg) { int i 10; while (i--) { sem_wait(sem_H); printf(hello ); fflush(stdout); sem_post(sem_W); } return NULL; } // 线程2输出World void *th2(void *arg) { int i 10; while (i--) { sem_wait(sem_W); printf(world\n); sleep(1); sem_post(sem_H); } return NULL; } int main(int argc, char **argv) { pthread_t tid1, tid2; // 初始化信号量sem_H1线程1可以直接执行sem_W0线程2等待 sem_init(sem_H, 0, 1); sem_init(sem_W, 0, 0); pthread_create(tid1, NULL, th1, NULL); pthread_create(tid2, NULL, th2, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); sem_destroy(sem_H); sem_destroy(sem_W); return 0; }四、死锁由于锁资源安排的不合理锁资源的申请和释放逻辑不对导致进程、线程无法正常继续执行推进的现象。产生死锁的四个必要条件互斥条件一个资源每次只能被一个进程使用。请求与保持条件一个进程因请求资源而阻塞时对已获得的资源保持不放。不剥夺条件进程已获得的资源在末使用完之前不能强行剥夺。循环等待条件若干进程之间形成一种头尾相接的循环等待资源关系。破坏任意一个就能避免死锁比如按固定顺序申请资源。