当前位置: 首页 > wzjs >正文

万峰科技著.asp.net网站开发四酷全书电子工业出版社360网站推广官网

万峰科技著.asp.net网站开发四酷全书电子工业出版社,360网站推广官网,大型网站开发团队的美工需要什么工作经验和教育背景及薪酬,如何做监控网站多线程补充 1. 线程互斥1.1 进程线程间的互斥相关背景概念1.2 互斥量mutex1.3 互斥量的接口1.3.1 互斥量的初始化1.3.2 销毁互斥量1.3.3 互斥量实现原理探究 1.4 可重入与线程安全1.4.1 线程安全1.4.2 重入 1. 线程互斥 1.1 进程线程间的互斥相关背景概念 ✅ 临界资源&#x…

多线程补充

  • 1. 线程互斥
    • 1.1 进程线程间的互斥相关背景概念
    • 1.2 互斥量mutex
    • 1.3 互斥量的接口
      • 1.3.1 互斥量的初始化
      • 1.3.2 销毁互斥量
      • 1.3.3 互斥量实现原理探究
    • 1.4 可重入与线程安全
      • 1.4.1 线程安全
      • 1.4.2 重入


1. 线程互斥

1.1 进程线程间的互斥相关背景概念

✅ 临界资源(Critical Resource)
多线程程序中,多个线程可能共享一些资源(比如一个变量、文件、内存块等)。这些共享资源就叫“临界资源”。

✅ 临界区(Critical Section)
临界区就是访问共享资源的那段代码。
在多线程程序里,如果多个线程都访问同一个共享变量,而这个访问操作又是读写、修改这些可能引发问题的,就属于“临界区”。

✅ 互斥(Mutual Exclusion)
为了解决这个问题,程序必须保证:
同一时刻最多只能有一个线程执行临界区中的代码。

其中详细说说临界区:

🧠 为什么要单独提“临界区”?
因为多个线程同时执行临界区的代码,就可能发生数据错乱、结果不一致、程序崩溃等问题。

所以:
我们不希望多个线程同时进入临界区
要用 互斥锁(mutex) 来保护临界区

举个例子讲清楚
🔴 没加锁的临界区(错误版):

if (ticket > 0) {printf("Sell ticket %d\n", ticket);ticket--;
}

这段代码访问了共享变量 ticket,所以它是临界区。

问题来了:
假设两个线程都看到 ticket == 1,都通过了 if 判断,然后都执行 ticket–,那最终就会卖出两张票(变成 ticket = -1),明显是错的。

✅ 加锁的临界区(正确版):

pthread_mutex_lock(&mutex);  // 加锁:别人不能进来了if (ticket > 0) {printf("Sell ticket %d\n", ticket);ticket--;
}pthread_mutex_unlock(&mutex);  // 解锁:别人可以进了

现在,我们把访问 ticket 的代码包进互斥锁中,这样任何时刻,只有一个线程能访问这段代码,就不会出问题了。

为什么要实现互斥?

int ticket = 100; // ✅ 共享资源:所有线程共享的票数变量void *route(void *arg) {char *id = (char*)arg;while (1) {if (ticket > 0) {  // 🟡 这是判断临界资源状态usleep(1000);  // 💤 模拟业务处理,容易引发线程切换(并发问题)usleep功能把进程挂起一段时间printf("%s sells ticket:%d\n", id, ticket);ticket--;      // 🔥 临界区(非原子操作)} else {break;}}
}

💥 多个线程如果“同时”执行 ticket–,就可能出现 数据竞争:

例子:

thread 1 和 thread 2 都看到 ticket == 1

都通过了 if (ticket > 0)

然后都执行了 ticket–

最终可能 ticket == -1 ❌,明明只有 1 张票,卖了两张

usleep(1000) 增加了出错几率(模拟现实)
它模拟的是一个“耗时操作”(比如打印票据、联系数据库)
这段代码一执行,线程就可能“被操作系统切换出去”
如果这时另一个线程也进来了,就会两个线程一起进入临界区

❗️总结这个问题:为什么不安全?

这段代码没有使用互斥锁保护临界区,所以:

多线程访问共享变量 ticket,会产生 竞态条件(竞态条件指两个或多个线程试图对共享资源进行操作时,由于线程执行的先后顺序不确定,导致程序的运行结果不可预测或错误的情况。 )非原子操作 ticket-- 在高并发下就会引发错误
usleep() 增加了被中断的概率,让问题更容易复现
结果就是:多卖、错卖、甚至负数票都可能出现

✅ 正确的做法:加互斥锁


1.2 互斥量mutex

🧱 什么是互斥量(mutex)?
互斥量是一种线程同步机制,用于控制对共享资源的访问。确保每次只有一个线程进入临界区。

💡 使用步骤
初始化 mutex
在进入临界区前 pthread_mutex_lock(&mutex);
在退出临界区后 pthread_mutex_unlock(&mutex);
最后销毁 pthread_mutex_destroy(&mutex);

可以用 pthread_mutex_t 来保护 ticket-- 部分,像这样:

// 多线程卖票 + 互斥锁控制pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
✅ 声明并初始化一个互斥锁(mutex)。
这把“锁”会保护我们共享的资源:ticket。
你也可以使用 pthread_mutex_init() 动态初始化,不过这里用宏更方便。线程执行的函数route()如下。传进来的arg是线程的名字
(比如 "thread 1""thread 2")用于打印区分。void *route(void *arg) {char *id = (char*)arg;while (1) {pthread_mutex_lock(&lock);  // 🔐 加锁,进入临界区
🧠 目的:确保“加锁后的代码”一次只能被一个线程执行。
⛔ 如果有两个线程同时跑到这,它们会排队执行临界区里的代码,避免数据混乱。if (ticket > 0) {usleep(1000); printf("%s sells ticket:%d\n", id, ticket);ticket--;pthread_mutex_unlock(&lock);  // 🔓 解锁,退出临界区💥 这块代码是关键的临界区代码多个线程不能同时执行,否则 ticket-- 会乱!} else {pthread_mutex_unlock(&lock);  // 🔓 别忘了解锁break;}}
}

pthread_mutex_lock

它是 Linux 多线程编程中用于“加锁”的函数,来自 POSIX 线程库(pthread)。

int pthread_mutex_lock(pthread_mutex_t *mutex);mutex:指向互斥锁的指针(在你的例子中是 &lock)。
返回值:
成功返回 0
失败返回错误码(比如死锁、锁已损坏等)

✅ 它做了什么事情?
当一个线程执行到 pthread_mutex_lock(&lock) 时:

如果这把锁是“空闲的”(没有其他线程持有):

当前线程立即获得这把锁,进入临界区。

如果这把锁已经被其他线程持有:

当前线程会阻塞等待,直到锁被释放。

🧠 它就像“上锁进入一个只能一个人进去的小房间”,别人只能在门外等着。

其中ticket–操作:
– 操作并不是原子操作,而是对应三条汇编指令:
load :将共享变量ticket从内存加载到寄存器中
update : 更新寄存器里面的值,执行-1操作
store :将新值,从寄存器写回共享变量ticket的内存地址


1.3 互斥量的接口

1.3.1 互斥量的初始化

互斥量(mutex)是用于多线程编程中控制对共享资源访问的工具,避免多个线程同时访问同一资源,导致数据不一致的情况。

方法1:静态分配

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

这是最简单的一种方式,在声明互斥量时直接进行初始化。PTHREAD_MUTEX_INITIALIZER 是一个常量,它初始化一个互斥量为默认状态,适合简单的场景。

方法2:动态分配

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

这种方法允许你在运行时初始化互斥量,可以为互斥量设置特定的属性。常见的属性包括锁的类型(如普通锁、递归锁等)。如果不需要特殊属性,可以将 attr 设置为 NULL,这样就会使用默认的属性。

mutex:指向互斥量的指针。

attr:指向互斥量属性的指针。如果不需要特别的属性,通常传入 NULL。

生活中的例子: 你可以把互斥量初始化比作准备好一个门。方法1是直接从厂家购买一个已经准备好的门,而方法2则是根据需要自己选择门的大小、样式等属性。

1.3.2 销毁互斥量

销毁互斥量是为了释放资源,但销毁时有一些注意事项。

int pthread_mutex_destroy(pthread_mutex_t *mutex);

重要注意事项:

如果使用 PTHREAD_MUTEX_INITIALIZER 初始化的互斥量,不需要调用 pthread_mutex_destroy(),因为它是静态初始化的,生命周期由操作系统管理。

不要销毁已经加锁的互斥量,否则会导致未定义行为。

确保不会在互斥量被销毁后继续加锁。

生活中的例子: 销毁互斥量就像是把门从房间里移走。你不能在别人还在门里时把门拆掉,否则会影响他们的使用。

互斥量加锁和解锁

加锁:pthread_mutex_lock

int pthread_mutex_lock(pthread_mutex_t *mutex);

这个函数用来给互斥量上锁,确保当前线程进入临界区时,其他线程无法进入。

如果当前互斥量没有被锁住,线程会成功加锁,进入临界区。

如果互斥量已经被其他线程加锁,调用这个函数的线程将会被阻塞,直到锁被释放。

解锁:pthread_mutex_unlock

int pthread_mutex_unlock(pthread_mutex_t *mutex);

当线程不再需要访问共享资源时,应该解锁互斥量,以便其他线程可以获得访问权。

生活中的例子: 假设你和朋友在玩一个游戏,需要共享一个遥控器。你们设定了一个规则:每次只有一个人能控制遥控器。

加锁:当你拿到遥控器时,你会锁住遥控器,其他朋友不能同时拿。其他朋友必须等你玩完才行。

解锁:当你完成你的操作后,释放遥控器,让其他朋友可以使用。

调用 pthread_mutex_lock 时的情况:

当一个线程调用 pthread_mutex_lock 时,可能会遇到以下两种情况:
互斥量为空闲:当前线程获得锁,进入临界区,继续执行。
互斥量已被其他线程锁住:当前线程会被阻塞,直到其他线程释放锁。

1.3.3 互斥量实现原理探究

在多线程程序中,如果多个线程访问共享资源时没有合适的同步机制,就容易导致数据一致性问题。例如,在没有锁的情况下,多个线程同时执行 ticket-- 这样的操作,就会导致票数不准确,因为每个线程对 ticket 的修改操作可能会发生在相同的时刻,从而互相覆盖,造成丢失更新。为了避免这种问题,我们需要使用互斥锁。

xchg(交换)指令是大多数体系结构提供的一种原子操作,它可以将寄存器的值与内存中的值交换,并且这个操作是不可中断的,也就是说它在多处理器平台上是线程安全的,能够确保交换操作的原子性。

lock操作:

lock :movb $0, mutex        ;0(表示锁未被占用)存入 mutexxchgb %al, mutex      ; 将寄存器 %al 的值与 mutex 的值交换if (al寄存器的内容 > 0) {return 0;         ; 如果原本 mutex 的值大于0,说明锁已经被占用,返回} else {挂起等待;         ; 否则,说明 mutex 的值为 0,锁已成功获取,继续执行goto lock;        ; 如果锁被其他线程占用,则继续等待}

通过 xchg(或 exchange)指令,锁的操作变得原子化,不会被中断,保证了多线程环境中的同步性。mutex 的状态只有在交换操作完成后才能被更改,这样即使有多个处理器或线程在同一时刻访问 mutex,它们也会被有效地同步,确保一次只有一个线程可以进入临界区。

这种方法通过硬件指令提供了高效的锁机制,并且由于操作的原子性,避免了线程间的竞态条件。


1.4 可重入与线程安全

1.4.1 线程安全

线程安全是指多个线程并发访问同一段代码时,不会出现数据竞态或不一致的情况,最终结果是正确的。在多线程环境中,如果没有适当的同步机制(如互斥锁、条件变量等),多个线程同时访问共享资源时,可能会导致数据损坏或不一致。

举个例子:
假设有多个线程同时对一个全局变量 counter 进行加1操作:

int counter = 0;void increment() {counter++;  // 非线程安全
}

如果不做任何保护,多个线程同时执行 counter++,可能会导致 counter 的值丢失,出现错误的结果。这就是 线程不安全 的一个例子。

为了使其 线程安全,我们可以使用互斥锁来保证每次只有一个线程可以访问 counter:

int counter = 0;
pthread_mutex_t mutex;void increment() {pthread_mutex_lock(&mutex);counter++;  // 现在是线程安全的pthread_mutex_unlock(&mutex);
}

这样就确保了每次只有一个线程能修改 counter,防止了数据竞争问题。

1.4.2 重入

重入性 是指同一个函数在不同的执行流中被再次调用,且不会因为前一个调用尚未结束,导致执行结果不一致或出现其他问题。如果一个函数在重入的情况下能正确工作,说明该函数是可重入的。如果不能正确工作,那么它就是不可重入的。

举个例子:
假设我们有一个递归函数:

void foo(int n) {if (n == 0)return;foo(n-1);
}

这个函数是 可重入的,因为它每次调用自己都会在不同的栈帧中执行,互不干扰。而 不可重入的 函数可能依赖于共享资源或全局变量,在多个执行流进入时,会因为数据被破坏而导致问题。

线程安全与重入性之间的区别与联系

线程安全与重入性:

线程安全的函数 是指该函数在多线程环境下能够正确工作,多个线程并发访问时,不会出现数据竞争问题。

可重入的函数 是指同一个函数在多个执行流(例如,递归或多线程环境)中被多次调用时,每次调用都能正确工作,不会因为其他调用的存在而出错。

联系:

可重入函数一定是线程安全的。如果一个函数可以在多个执行流中被调用,而不出现数据损坏或状态冲突,那它肯定是线程安全的。因为多线程中的每个线程相当于一个独立的执行流,如果函数是可重入的,那么它不会被其他线程的执行影响,因此也不会引发线程安全问题。

线程安全的函数不一定是可重入的。即使一个函数在多线程环境中使用锁来保证线程安全,但如果它不是设计为可重入的,那么可能会出现死锁问题。例如,如果一个线程在执行该函数时持有锁并进入了该函数的某个部分,而同一线程又因为某种原因在函数内部的另一部分重入该函数,那么就可能引发死锁。这种情况下,这个函数虽然是线程安全的,但却是不可重入的。

不可重入的情况:
调用 malloc/free 等函数: malloc 和 free 是用全局链表来管理内存的,它们的实现通常依赖于全局变量,因此如果多个线程在并发调用 malloc 或 free 时,会导致数据结构的损坏,导致内存泄漏或重复释放内存等问题。

标准 I/O 库函数: 标准 I/O 库(如 printf、scanf)的很多实现依赖于全局静态变量,因此它们在多线程环境下是不可重入的。如果多个线程同时调用这些函数,可能会干扰彼此,导致输出错乱。

可重入的情况:

没有全局变量或静态变量的函数: 如果一个函数内部只使用局部变量,并且这些变量的数据在每次调用时都是独立的,那么这个函数是可重入的。例如:

void add(int a, int b) {int result = a + b;printf("%d\n", result);
}

这个函数是可重入的,因为它的执行不依赖任何全局或静态变量,每个线程或递归调用都会独立地完成自己的任务。

不调用不可重入的函数: 如果函数内部没有调用 malloc、free 或其他不可重入的函数,且没有共享全局数据,那么它就是可重入的。例如:

int multiply(int a, int b) {return a * b;
}
http://www.dtcms.com/wzjs/246910.html

相关文章:

  • 设计配色的网站推广网络营销案例
  • 孝感个人网站建设沈阳网站关键词优化多少钱
  • 产品设计师网站优化服务是什么意思
  • 手机网站制作平台有哪些直接进入网站的代码
  • 网站建设培训龙岗每日新闻简报
  • 海珠网站建设公司龙岩seo
  • 重点专业建设网站 建设方案网站收录平台
  • 学计算机网站开发好吗网络运营培训班多少钱
  • 大学生个人网站怎么做北京十大营销策划公司
  • 做新闻的网站怎样赚钱天津百度推广公司电话
  • WordPress日历样式云seo关键词排名优化软件
  • 古典风网站泾县网站seo优化排名
  • 正规的网站建设公司net的网站建设
  • 怎么查网站做404页面没营销软件哪个好
  • 郑州网站开发网络营销总监岗位职责
  • 标准件做啥网站南通百度seo代理
  • 网络推广和网站推广的关系网站的seo方案
  • 专门做正品的网站小程序开发教程
  • 用四字成语做网站域名好吗国外比较开放的社交软件
  • 哪个网站做h5最好360推广平台登录入口
  • 部门网站建设自查报告百度seo自动优化
  • 做矿业的郑州公司网站企业网站推广方法
  • 豆瓣 wordpress 插件短视频seo公司
  • 北京做企业网站的公司品牌营销是什么
  • 聊城做网站多少钱app拉新推广代理
  • 本地网站建设教程xampp图片外链在线生成网址
  • php网站开发介绍怎么创作自己的网站
  • 建设菠菜网站杭州seo软件
  • 网站dns刷新网站推广系统
  • 建设动态网站的目的公司网址