作者: 027导航

  • JavaScript逻辑或(||)运算符的正确用法与多值比较策略

    JavaScript逻辑或(||)运算符的正确用法与多值比较策略

    JavaScript逻辑或(||)运算符的正确用法与多值比较策略

    本文深入探讨JavaScript中逻辑或(||)运算符的正确用法,特别是在进行多值条件判断时常见的误区。我们将通过D&D角色职业选择的实际案例,揭示 if (variable === 'val1' || 'val2') 这种写法的错误原因,并提供 if (variable === 'val1' || variable === 'val2') 的正确实现。此外,还将介绍 Array.prototype.includes() 和 switch 语句等更优雅的多值比较策略,帮助开发者编写更健壮、可读性强的条件逻辑。

    在javascript中,逻辑或运算符 || 用于连接两个或多个表达式。它的基本工作原理是:如果第一个操作数是“真值”(truthy),则返回第一个操作数的值;否则,它会评估第二个操作数并返回其值。这个过程被称为“短路评估”(short-circuit evaluation)。

    在条件语句(如 if 语句)中,|| 运算符的返回值会被隐式转换为布尔值进行判断。这意味着只要 || 运算符的任何一个操作数被评估为真值,整个条件表达式就会被视为 true。

    JavaScript中的真值和假值:

    • 假值(Falsy values):false, 0, -0, 0n (BigInt zero), "" (empty string), null, undefined, NaN。
    • 真值(Truthy values):除上述假值之外的所有值,包括非空字符串、非零数字、对象、数组等。

    许多初学者在使用 || 运算符进行多值比较时,会遇到一个常见的误区。他们可能期望 if (variable === "value1" || "value2") 能够判断 variable 是否等于 "value1" 等于 "value2"。然而,这种写法并不会按照预期工作。

    让我们通过一个具体的D&D角色职业选择案例来分析:

    立即学习“Java免费学习笔记(深入)”;

    假设我们有一个HTML select 元素,用于选择角色职业:

    然后,在JavaScript中尝试根据选择的职业进行判断:

    当 characterClass 的值为 "artificer" 时,这段代码的输出却是 "Squishy boi",而不是预期的 "Not a wizard/sorcerer"。这是因为 if (characterClass === "wizard" || "sorcerer") 这个条件表达式被错误地解析了:

    1. 首先评估 characterClass === "wizard"。由于 characterClass 是 "artificer",所以 characterClass === "wizard" 的结果是 false。
    2. 因为第一个操作数是 false,|| 运算符会继续评估第二个操作数,即 "sorcerer"。
    3. 字符串 "sorcerer" 是一个非空字符串,在JavaScript中被视为真值(truthy value)。
    4. 因此,整个条件表达式 false || true 的结果是 true。
    5. 最终,if 语句的条件被判断为 true,从而执行了 console.log("Squishy boi")。

    要正确地使用 || 运算符进行多值比较,必须为每个比较条件重复变量本身:

    1. 重复比较法

    这是最直接和明确的修正方式。为 || 运算符的每个部分都提供一个完整的比较表达式:

    现在,如果 characterClass 是 "artificer":

    1. characterClass === "wizard" 结果为 false。
    2. characterClass === "sorcerer" 结果为 false。
    3. 整个条件表达式 false || false 的结果是 false。
    4. if 语句的条件被判断为 false,从而执行 else 块中的 console.log("Not a wizard/sorcerer")。这符合预期。

    2. 更优雅的多值比较策略

    当需要比较的值数量较多时,重复比较法可能会导致代码冗长且不易维护。以下是两种更优雅的替代方案:

    a. 使用 Array.prototype.includes()

    includes() 方法可以判断一个数组是否包含某个指定的值,返回 true 或 false。这使得它非常适合用于检查一个变量是否在多个预设值之中。

    这种方法尤其适用于当需要比较的职业列表很长时,代码会更加简洁和可读。

    b. 使用 switch 语句

    当根据一个变量的不同值需要执行不同的操作时,switch 语句是一个非常清晰的选择。

    在 switch 语句中,如果多个 case 块需要执行相同的代码,可以像上面那样将它们连续排列,而不使用 break,这样会“穿透”到下一个 case,直到遇到 break 或 switch 块结束。

    • 理解 || 的短路特性:|| 运算符会从左到右评估操作数,一旦遇到真值,就会立即返回该真值,而不再评估后续的操作数。这是导致 if (variable === "value1" || "value2") 行为异常的根本原因。
    • 区分表达式和值:在条件判断中,每个 || 运算符的操作数都应该是一个完整的布尔表达式(例如 variable === "value"),而不是一个单独的值(例如 "value2")。
    • 选择合适的比较策略

      • 对于两个或三个值的简单比较,重复比较法(a === b || a === c)足够清晰。
      • 当需要比较的值数量较多时,Array.prototype.includes() 提供了一种更简洁、更具可读性的解决方案。
      • 当需要根据变量的不同值执行不同的代码块时,switch 语句是更好的选择,它能使逻辑结构更清晰。

    通过深入理解 || 运算符的工作原理并掌握正确的比较策略,开发者可以避免常见的逻辑错误,编写出更健壮、更易于维护的JavaScript代码。

    以上就是JavaScript逻辑或(||)运算符的正确用法与多值比较策略的详细内容,更多请关注php中文网其它相关文章!

  • win10网络图标一直转圈怎么办_win10网络连接图标转圈无法上网的解决方法

    win10网络图标一直转圈怎么办_win10网络连接图标转圈无法上网的解决方法

    首先重启网络列表服务并检查启动类型,其次更新或重装网卡驱动,接着重置网络设置恢复默认配置,最后运行系统自带的网络疑难解答工具以自动修复问题。

    win10网络图标一直转圈怎么办_win10网络连接图标转圈无法上网的解决方法

    如果您尝试访问网络时,发现Windows 10的网络连接图标持续转圈,这通常表示系统正在尝试识别网络状态但未能完成。此类问题可能与后台服务、驱动程序或网络配置有关。以下是解决此问题的具体步骤:

    本文运行环境:Dell XPS 13,Windows 10 专业版

    网络列表服务(Network List Service)负责管理网络连接状态的识别和显示。如果该服务未正常运行,可能导致网络图标持续转圈。

    1、按下键盘上的Win + R组合键,打开“运行”窗口。

    2、输入services.msc并按回车,进入服务管理界面。

    3、在服务列表中找到Network List Service,双击打开其属性设置。

    4、将“启动类型”设置为自动,然后点击“启动”按钮以运行该服务。若服务已运行,可先点击“停止”,再点击“启动”进行重启。

    5、关闭服务窗口,观察任务栏网络图标是否恢复正常。

    win10网络图标一直转圈怎么办_win10网络连接图标转圈无法上网的解决方法

    过时、损坏或不兼容的网卡驱动程序可能导致系统无法正确识别网络状态,从而引发图标转圈现象。

    1、右键点击桌面左下角的开始菜单,选择“设备管理器”。

    2、在设备管理器中展开网络适配器类别,查看是否有设备带有黄色感叹号或问号。

    3、右键点击您的无线或有线网卡设备,选择更新驱动程序,然后选择“自动搜索驱动程序”。

    4、如果自动更新无效,可选择“浏览我的计算机以查找驱动程序”,手动指定已下载的最新驱动程序进行安装。

    5、若仍无法解决,可右键选择卸载设备,勾选“删除此设备的驱动程序软件”,确认后重启电脑,系统将自动重新安装驱动。

    win10网络图标一直转圈怎么办_win10网络连接图标转圈无法上网的解决方法

    网络配置文件损坏或设置冲突也可能导致连接状态异常。重置网络可恢复默认配置,排除此类问题。

    1、打开设置应用,点击进入“网络和Internet”。

    2、在左侧菜单中选择状态,向下滚动至页面底部。

    3、点击网络重置选项,系统会提示此操作将删除所有网络适配器并重新安装。

    4、点击“立即重置”按钮,并在确认对话框中选择“是”。

    5、等待系统完成重置过程,完成后重启计算机,检查网络图标状态。

    win10网络图标一直转圈怎么办_win10网络连接图标转圈无法上网的解决方法

    Windows 10内置的网络疑难解答工具可以自动检测并修复常见的网络连接问题,包括图标异常。

    1、右键点击任务栏中的网络图标,选择疑难解答

    2、系统将自动开始诊断网络连接问题,此过程可能需要几分钟。

    3、根据诊断结果,系统会提供修复建议并尝试自动解决问题。

    4、若提示需要管理员权限,请点击尝试以管理员身份进行这些修复,并确认操作。

    5、修复完成后,查看网络图标是否停止转圈并正常显示连接状态。

    以上就是win10网络图标一直转圈怎么办_win10网络连接图标转圈无法上网的解决方法的详细内容,更多请关注php中文网其它相关文章!

  • thinkphp报错“cURL error 60”SSL证书问题怎么解决

    thinkphp报错“cURL error 60”SSL证书问题怎么解决

    答案是cURL error 60因SSL证书验证失败导致,可通过配置CA证书、指定证书路径或临时关闭验证解决,推荐下载cacert.pem并配置php.ini中curl.cainfo以实现安全稳定的HTTPS请求。

    thinkphp报错“curl error 60”ssl证书问题怎么解决

    ThinkPHP 报错“cURL error 60”通常是由于 cURL 请求 HTTPS 接口时无法验证 SSL 证书导致的。这个错误的完整提示一般是:

    SSL certificate problem: unable to get local issuer certificate

    这意味着 PHP 的 cURL 扩展找不到受信任的 CA(证书颁发机构)证书来验证目标服务器的 SSL 证书。以下是几种常见且有效的解决方法。

    最安全的做法是让 cURL 使用正确的 CA 证书包进行验证。

    立即学习“PHP免费学习笔记(深入)”;

    • 前往 https://www.php.cn/link/5fe4dadcdb001d8566cd20e6d8a20251 下载最新的 cacert.pem 文件
    • 将文件保存到你的服务器某个目录,例如: 或
    • 修改 php.ini 配置:

    (Windows)

    (Linux)

    • 重启 Web 服务(Apache/Nginx)和 PHP-FPM

    这样全局的 cURL 请求都会使用可信的证书进行验证,安全性高。

    如果你使用的是 ThinkPHP 自带的 Http 或 SwooleHttpClient,可以在请求时指定 CA 证书路径:

    示例代码:

    或者关闭严格验证(仅开发环境建议):

    如果只是测试或调试,可以临时跳过证书验证(有安全风险):

    或在调用时直接关闭:

    ⚠️ 注意:这会使请求容易受到中间人攻击,仅用于本地调试。

    SSL 验证还依赖系统时间。如果服务器时间不准确(如相差几天),也可能导致证书被视为无效。

    • Linux:运行 查看时间,使用 同步
    • Windows:确保时间与时区设置正确

    基本上就这些。优先推荐第1种方式,从根本上解决问题,既安全又稳定。开发阶段可用第3种临时绕过,但上线前务必恢复验证。

    以上就是thinkphp报错“cURL error 60”SSL证书问题怎么解决的详细内容,更多请关注php中文网其它相关文章!

  • Pandas Timestamp如何生成带冒号的时区指示符

    Pandas Timestamp如何生成带冒号的时区指示符

    Pandas Timestamp如何生成带冒号的时区指示符

    在Pandas中,当需要将Timestamp对象格式化为包含带冒号的时区偏移(如+00:00)的字符串时,直接使用Python标准库的strftime('%:z')指令会导致ValueError。本教程将深入探讨这一限制,并提供一个简洁有效的解决方案:利用pandas.Timestamp.isoformat()方法,该方法能够轻松生成符合ISO 8601标准的、包含冒号时区指示符的时间字符串,确保时间数据格式的准确性和一致性。

    pandas的timestamp对象提供了强大的时间处理能力,其strftime方法旨在与python标准库的datetime模块保持高度兼容。根据官方文档,strftime应支持大部分python原生strftime指令。然而,在实践中,尝试使用'%:z'指令来获取带冒号的时区偏移(例如+00:00)时,pandas的strftime方法会抛出valueerror: invalid format string。

    例如,以下代码在Python的datetime对象上可以正常工作,但在Pandas Timestamp上则会失败:

    这表明尽管'%:z'是Python datetime模块中一个有效的、用于生成带冒号时区偏移的指令,但Pandas的strftime实现并未完全采纳或支持此特定指令,导致了格式化需求的阻碍。

    为了克服strftime('%:z')的限制并生成包含带冒号时区偏移的字符串,最直接且推荐的方法是使用pandas.Timestamp.isoformat()。此方法专门设计用于生成符合ISO 8601标准的日期和时间字符串,而ISO 8601标准要求时区偏移中包含冒号。

    isoformat()方法提供了一些参数来灵活控制输出格式:

    • sep: 用于分隔日期和时间部分的字符。默认是'T',但可以设置为' '以匹配常见的日期时间格式。
    • timespec: 指定时间部分的精度。例如,'seconds'表示只包含时、分、秒,'milliseconds'或'microseconds'则包含更高的精度。

    以下是如何使用isoformat()方法来达到所需格式的示例:

    运行上述代码,你将得到类似 2023-12-04 16:08:02+00:00 这样的输出,其中时区偏移 +00:00 正是带有冒号的所需格式。

    1. 符合标准: isoformat()生成的字符串严格遵循ISO 8601标准,这对于数据交换和跨系统兼容性非常重要。
    2. 简洁高效: 相比于手动构建字符串或尝试通过其他方法插入冒号,isoformat()提供了一个内置的、简洁的解决方案。
    3. 时区处理: isoformat()能够正确处理Timestamp对象的时区信息,并将其转换为相应的UTC偏移量。
    4. 精度控制: timespec参数提供了对时间精度(秒、毫秒、微秒等)的灵活控制,可以根据具体需求调整输出。

    注意事项:

    • 确保你的Timestamp对象已经包含了时区信息。如果Timestamp是“天真”(naive)的,即没有时区信息,isoformat()将不会在输出中包含时区偏移。
    • isoformat()默认的日期时间分隔符是'T'。如果需要像'YYYY-MM-DD HH:MM:SS+HH:MM'这样的格式,请务必将sep参数设置为' '。

    尽管Pandas Timestamp的strftime方法在处理'%:z'指令时存在局限性,但pandas.Timestamp.isoformat()提供了一个强大且符合标准的替代方案。通过合理利用isoformat()及其timespec和sep参数,开发者可以轻松地生成包含带冒号时区偏移的日期时间字符串,满足各种数据格式化和集成需求。在需要精确控制时间字符串格式,特别是涉及时区偏移表示时,isoformat()是Pandas用户值得信赖的首选工具。

    以上就是Pandas Timestamp如何生成带冒号的时区指示符的详细内容,更多请关注php中文网其它相关文章!

  • 没用D加密! 《消逝的光芒:困兽》发售几小时内惨遭破解

    没用D加密! 《消逝的光芒:困兽》发售几小时内惨遭破解

    新的一周带来了备受期待的3a大作登场。本周最受关注的作品当属《消逝的光芒:困兽》与《索尼克赛车:交叉世界》。然而,伴随着新作发布,盗版活动也再度抬头。

    没用D加密! 《消逝的光芒:困兽》发售几小时内惨遭破解

    最早由红迪论坛用户曝光,《消逝的光芒:困兽》已确认被破解——这款作品在正式发售仅数分钟内便遭盗版组织攻陷,步上了《合金装备Δ:食蛇者》《最后生还者2》以及《漫威蜘蛛侠2》PC版的后尘,成为又一款发售后即刻沦陷的大作。

    没用D加密! 《消逝的光芒:困兽》发售几小时内惨遭破解

    这一情况与成功抵御破解的《无主之地4》形成强烈反差,后者得益于Denuvo加密技术的保护至今未被攻破。而《困兽》因未采用任何有效的反盗版机制,导致其防护形同虚设。值得注意的是,此次事件与《空洞骑士:丝之歌》发布时的情形大相径庭——当时尽管游戏被破解,但盗版社区内部曾自发发起抵制盗版的呼声。

    尽管面临迅速被盗版的困境,《困兽》在玩家和媒体间的评价却相当出色。游戏斩获IGN 7分、M站评分高达78分,不仅超越了初代《消逝的光芒》(74分)和《消逝的光芒2》(76分),更成为该系列迄今为止口碑最佳的一作。

    没用D加密! 《消逝的光芒:困兽》发售几小时内惨遭破解

    没用D加密! 《消逝的光芒:困兽》发售几小时内惨遭破解

    以上就是没用D加密! 《消逝的光芒:困兽》发售几小时内惨遭破解的详细内容,更多请关注php中文网其它相关文章!

  • 时间旅者重生曙光公寓自由结局怎么达成 自由结局解锁攻略

    时间旅者重生曙光公寓自由结局怎么达成 自由结局解锁攻略

    时间旅者重生曙光游戏中,想要达成结局自由,在主要反派研究员被击败后,选择干掉就能达成,且会获得成就“我自由”。

    时间旅者重生曙光公寓自由结局怎么达成 自由结局解锁攻略

    时间旅者重生曙光公寓自由结局怎么达成

    在主要反派——研究员——被击败后,你将有一个选择:干掉他或饶他一命。

    然而,故事的结局可能会有所不同。在游戏的最后,没有什么能阻止你射击研究员。

    在这种情况下,您将获得成就“我自由”。

    时间旅者重生曙光公寓自由结局怎么达成 自由结局解锁攻略

    ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​以上就是PHP中文网整理的时间旅者重生曙光自由结局怎么达成的相关内容啦,希望对你有所帮助,更多游戏攻略尽在PHP中文网。​​​​​​​​​​​​​​​​​​​​​​​​​​​​

    以上就是时间旅者重生曙光公寓自由结局怎么达成 自由结局解锁攻略的详细内容,更多请关注php中文网其它相关文章!

  • 解决 Angular 路由重定向与默认路径问题

    解决 Angular 路由重定向与默认路径问题

    解决 Angular 路由重定向与默认路径问题

    本教程旨在解决 Angular 应用中路由重定向不生效及默认页面不显示的问题。核心解决方案是引入通配符路由 (**),以捕获所有未匹配的URL路径,并将其重定向到指定页面,确保应用在任何情况下都能正确导航到预期的默认登录页。

    angular 路由是构建单页应用 (spa) 的核心机制,它允许用户在不重新加载整个页面的情况下,在不同视图之间进行导航。通过配置路由,我们可以将特定的 url 路径映射到相应的组件。

    一个典型的 Angular 路由配置涉及以下几个关键部分:

    • AppRoutingModule: 专门用于管理路由配置的模块。
    • Routes 数组: 定义了从 URL 路径到组件的映射关系,以及重定向规则。
    • RouterModule.forRoot(routes): 在根模块中导入并配置路由,使其在整个应用中可用。
    • <router-outlet>: 在 app.component.html 中放置此标签,作为路由组件渲染的占位符。

    在 Angular 路由配置中,redirectTo 和 pathMatch 是实现重定向的关键属性。

    • redirectTo: 指定当当前路径匹配时,应该重定向到的目标路径。
    • pathMatch: 定义了路径匹配策略。

      • 'prefix' (默认值): 只要 URL 的开头部分与 path 匹配,就会触发路由。
      • 'full': 只有当整个 URL 路径与 path 完全匹配时,才会触发路由。

    例如,{ path: '', redirectTo: '/login', pathMatch: 'full' } 表示当 URL 路径为空(即应用的根路径)时,将其完全匹配并重定向到 /login 路径。

    开发者在使用 Angular 路由时,有时会遇到设置了默认重定向(如将根路径 '' 重定向到 /login)但页面依然空白,或重定向不生效的问题。这可能由多种原因引起,包括路由配置顺序、base href 设置不当,或者缺少一个能够捕获所有未匹配路径的通用规则。

    在初始的路由配置中,我们可能已经定义了如下规则:

    尽管这条规则看起来正确,但在某些情况下,如果存在其他未被正确处理的路径,或者应用启动时路由状态未能完全初始化,页面仍可能无法按预期显示登录组件。

    解决默认路由重定向不生效或页面空白问题的有效方法是引入一个通配符路由 (**)。通配符路由是一个特殊的路由,它会匹配所有未被前面任何路由规则匹配的 URL 路径。这提供了一个强大的回退机制,确保应用始终能处理任何无效或未知的 URL。

    通过将通配符路由配置为重定向到应用的根路径(或直接重定向到默认页面),我们可以构建一个健壮的导航流程。

    为了解决上述问题,我们需要在 app-routing.module.ts 的 routes 数组中添加一个通配符路由。

    修改前的 app-routing.module.ts (部分):

    修改后的 app-routing.module.ts:

    添加 path: '**', redirectTo: '' 路由后,整个导航流程将更加健壮:

    1. 用户访问应用根路径 (http://localhost:4200/):

      • 路由系统首先尝试匹配 { path: '', redirectTo: '/login', pathMatch: 'full' }。
      • 路径完全匹配,用户被重定向到 /login。
      • LoginComponent 被渲染。
    2. 用户访问未知或无效路径 (http://localhost:4200/some-invalid-path):

      • 路由系统尝试匹配所有已定义的特定路由 (/login, /register, /dashboard),但均不匹配。
      • 最终,路由系统匹配到 { path: '**', redirectTo: '' }。
      • 用户被重定向到应用的根路径 ''。
      • 此时,路由系统再次处理根路径 '',并匹配到 { path: '', redirectTo: '/login', pathMatch: 'full' }。
      • 用户最终被重定向到 /login。
      • LoginComponent 被渲染。

    通过这种链式重定向,无论是访问根路径还是任何无效路径,用户最终都会被引导到 /login 页面,从而避免了页面空白或导航错误。

    在配置 Angular 路由时,除了通配符路由,还有一些重要的注意事项和最佳实践:

    • 路由顺序至关重要: Angular 路由会按照 routes 数组中定义的顺序进行匹配。因此,通配符路由 (``) 必须始终放在所有特定路由的最后**。如果将其放在前面,它将捕获所有请求,导致后续的特定路由无法被访问。
    • base href 配置: 确保 index.html 文件中的 <base href="https://www.php.cn/"> 标签正确设置。它告诉浏览器在解析相对 URL 时,应以哪个路径作为基础。对于大多数单页应用,设置为 / 是常见的做法。
    • router-outlet 存在性: 确认你的根组件 (app.component.html) 中包含了 <router-outlet></router-outlet> 标签。这是 Angular 渲染路由组件的必需占位符。如果缺少,即使路由匹配成功,组件也无法显示。
    • 组件声明: 确保所有作为路由目标的组件(如 LoginComponent, RegisterComponent, DashboardComponent)都已在 AppModule 或其他相关模块的 declarations 数组中声明。否则,Angular 将无法识别并加载这些组件。
    • 调试技巧: 当路由出现问题时,可以利用浏览器开发者工具进行调试。

      • 网络请求: 检查是否有预期的组件文件被加载。
      • 控制台错误: 查找与路由或组件加载相关的错误信息。
      • Angular 路由事件: 可以在 AppModule 中订阅 Router 服务的事件流 (router.events.subscribe(...)),打印路由生命周期事件,帮助理解导航过程。

    Angular 路由是构建复杂应用的关键。通过正确配置 redirectTo、pathMatch 和最重要的通配符路由 (``)**,我们可以确保应用在任何情况下都能提供预期的导航体验。通配符路由不仅能处理未知路径,还能与默认重定向结合,为用户提供一个始终可达的起始点,如登录页面。遵循上述最佳实践,将有助于构建一个稳定、用户友好的 Angular 应用。

    以上就是解决 Angular 路由重定向与默认路径问题的详细内容,更多请关注php中文网其它相关文章!

  • C++如何使用std::atomic保证线程安全

    C++如何使用std::atomic保证线程安全

    std::atomic通过原子操作确保线程安全,适用于单变量无锁编程,性能高但需谨慎使用内存序;而std::mutex提供更通用的互斥保护,适合复杂操作和数据结构,易于正确使用。选择取决于场景:简单原子操作用std::atomic,复合逻辑用std::mutex。

    c++如何使用std::atomic保证线程安全

    C++中, 提供了一种机制,确保对共享变量的操作是原子性的,即这些操作要么完全执行,要么根本不执行,并且在执行过程中不会被其他线程中断。这正是实现线程安全,避免数据竞争和未定义行为的核心手段。

    是C++标准库提供的一个模板类,它封装了一个类型,并为其提供了一系列原子操作。当我们说一个操作是“原子性的”,意味着它在多线程环境下是不可分割的。例如,一个简单的 看起来是一个操作,但在底层,它可能被分解为“读取 的值”、“将值加一”、“将新值写入 ”这三个步骤。如果在多线程环境中,另一个线程可能在“读取”和“写入”之间修改了 的值,导致数据不一致。 就是为了解决这类问题而生。

    使用 非常直观,你只需要将需要原子操作的类型封装起来:

    在这个例子中, 实际上是一个原子性的“读取-修改-写入”操作(fetch_add)。 用于原子地读取当前值, 用于原子地写入一个值。此外, 还提供了 和 等更复杂的原子操作,它们是实现无锁算法(lock-free algorithms)的基石。这些操作允许你原子地比较一个值,并在满足条件时替换它,非常适合实现自旋锁或无锁数据结构。

    立即学习“C++免费学习笔记(深入)”;

    这是并发编程中一个非常常见且关键的问题。 和 都是用于实现线程安全的工具,但它们的设计理念、适用场景和性能特性有着显著的不同。

    主要关注单个变量的原子操作。它通常是“无锁的”(lock-free),这意味着它通过底层CPU指令(如CAS,Compare-And-Swap)来保证操作的原子性,而不需要操作系统层面的互斥锁。无锁操作的优势在于,它避免了线程上下文切换的开销,理论上在竞争不激烈或只涉及单个简单数据类型时能提供更好的性能。它的粒度非常细,只保护单个变量的读写或修改。然而, 无法保证多个操作序列的原子性,也不能保护复杂的数据结构。

    (互斥锁)则提供了一种更粗粒度的同步机制。它通过“锁定”一段代码区域或一组数据来防止多个线程同时访问。当一个线程获得互斥锁时,其他试图获取该锁的线程会被阻塞,直到锁被释放。 可以保护任意复杂的代码逻辑和数据结构,确保在锁定的范围内,数据的一致性和操作的原子性。但它的缺点是引入了锁的开销,包括锁的获取、释放以及可能的线程上下文切换,这在竞争激烈或锁持有时间较长时可能成为性能瓶颈。

    何时选择哪个?

    • 选择 :

      • 当你只需要对单个简单数据类型(如 , , 指针)进行原子性的读、写、增、减或比较交换操作时。
      • 当你追求极致性能,且确信无锁方案能够满足需求时(但请注意,无锁编程比有锁编程更难正确实现)。
      • 例如,计数器、标志位、共享指针的引用计数等场景。
      • 你可以通过 来检查特定类型在当前平台上是否真的是无锁的。
    • 选择 :

      • 当你需要保护一段包含多个操作的代码逻辑,确保这些操作作为一个整体是原子性的。
      • 当你需要保护复杂的数据结构(如链表、哈希表、队列等),因为对这些结构的修改通常涉及多个步骤, 无法单独保证这些步骤的原子性。
      • 当你发现 无法满足你的复杂同步需求,或者无锁算法实现起来过于复杂且容易出错时。
      • 例如,修改银行账户余额(涉及读取、计算、写入多个步骤)、向共享队列添加或移除元素等场景。

    有时候,两者甚至可以结合使用。例如,一个无锁队列可能在内部使用 来管理头尾指针,但对于队列元素的实际数据访问,可能仍然需要 或其他更复杂的无锁设计来保证数据一致性。我的经验是,如果不是对性能有极高的要求,且对并发模型有深入理解,通常会优先考虑 ,因为它更容易理解和正确使用。

    的强大之处不仅在于其原子操作本身,更在于其通过“内存序”(Memory Order)机制,允许开发者在性能和正确性之间进行精细的权衡。内存序定义了原子操作如何与程序中的其他内存操作(包括非原子操作)进行排序,从而影响不同线程观察到事件的顺序。理解这一点是掌握 的关键,也是并发编程中最容易出错但也最具优化潜力的部分。

    默认情况下, 的所有操作都使用 (顺序一致性)。这是最强也是最安全的内存序,它保证所有线程观察到的所有原子操作的顺序都是一致的,就像所有操作都发生在一个全局的单一时间线上一样。这种强保证通常通过内存屏障(memory barrier/fence)实现,可能会引入额外的性能开销,因为它限制了编译器和CPU的优化能力。

    然而,在许多场景下,我们并不需要如此强的保证。C++11引入了多种内存序,允许我们根据实际需求放松这些限制:


    1. 这是最弱的内存序。它只保证原子操作本身的原子性,不提供任何跨线程的排序保证。也就是说,一个线程对一个 原子变量的写入,可能在另一个线程观察到该写入之前,先观察到该线程后续的其他非原子操作。它最快,但使用起来最危险,因为它完全依赖于程序员对并发模型的精确理解。通常只在计数器或一些不关心相对顺序的场景中使用。

    2. 和 :
      这两个内存序通常成对使用,用于建立“同步-发生”(synchronizes-with)关系,从而创建“先于发生”(happens-before)关系。

      • 确保当前线程中,所有在 操作之前的内存写入,都会在 操作完成后对其他线程可见。它就像一道门,保证在门关闭之前,所有东西都已就位。
      • 确保当前线程中,所有在 操作之后的内存读取,都能看到在 操作之前由其他线程 的所有写入。它就像一道门,保证在门打开之后,所有东西都已可见。
        当一个线程执行 操作,另一个线程执行 操作并成功看到 的结果时, 操作之前的所有内存写入都会对 操作之后的所有内存读取可见。这在生产者-消费者模型中非常有用。

      在这个例子中, 对保证了当 看到 为 时,它也一定能看到 被设置为 。


    3. 结合了 和 的语义,通常用于读-修改-写(RMW)操作,如 或 。它既保证了操作之前的写入对其他线程可见,也保证了操作之后的读取能看到其他线程的最新写入。

    权衡与选择:

    内存序的选择是一个微妙的平衡。 最安全,但可能牺牲性能。 最快,但最容易引入难以调试的并发错误。 对提供了一种中间地带,在保证特定同步需求的同时,允许更大的优化空间。

    我个人在实践中,如果对并发模型没有百分之百的把握,或者性能瓶颈并不明显,我会倾向于使用默认的 。它的“傻瓜式”安全性,能帮我避免很多头痛的问题。只有当性能分析明确指出原子操作的内存序是瓶颈时,我才会谨慎地考虑使用更弱的内存序。这通常需要对目标硬件的内存模型有一定了解,并进行大量的测试来验证正确性。毕竟,一个难以复现的并发bug,其代价往往远高于一点点性能提升。

    虽强大,但并非银弹。在使用它时,开发者常常会遇到一些陷阱,同时也积累了一些最佳实践。

    常见的陷阱:

    1. 误以为所有操作都原子化: 只保证其自身成员函数的原子性。如果你有一个 ,然后尝试 或者 ,这不是原子操作。 会原子地获取 的一个副本,然后你修改的是这个副本,而不是共享的 本身。只有对 完整的赋值()或读取()才是原子性的。对于复杂类型,你可能需要使用 或自己设计无锁结构。
    2. 忘记内存序的重要性: 默认的 虽安全但可能效率不高,而为了性能盲目使用 几乎肯定会引入难以追踪的并发bug。如前所述,不理解内存序而随意使用,是导致并发错误的主要原因之一。
    3. ABA 问题: 当使用 或 实现无锁算法时,可能会遇到 ABA 问题。即一个值从 A 变为 B,然后又变回 A。如果你的算法仅仅是检查值是否仍然是 A,那么它可能会错误地认为没有发生变化,从而导致逻辑错误。解决办法通常是引入一个版本号或标记,每次修改时也原子地更新版本号。
    4. 不适合保护复杂数据结构: 适用于单个、简单的数据类型。它无法直接保护像链表、树、队列等复杂数据结构。对这些结构的修改通常涉及多个步骤(如修改指针、更新计数、分配/释放内存),这些步骤需要作为一个整体进行保护,这通常需要 或更复杂的无锁算法设计。
    5. 性能错觉: 尽管 提供了无锁操作,但在高竞争环境下,反复的 失败循环(自旋)可能会导致CPU资源浪费,甚至比使用互斥锁的性能更差。无锁不等于无开销。

    最佳实践:

    1. 从小处着手,保持简单: 优先将 用于最简单的场景,例如计数器、布尔标志、单点指针等。只有当这些简单场景能够被原子操作完全覆盖时,才考虑使用它。
    2. 默认使用 : 这是最安全的起点。除非你对内存模型有深刻理解,并且有明确的性能瓶颈需要优化,否则不要轻易尝试更弱的内存序。正确性永远是第一位的。
    3. 理解 的语义: 这是构建无锁算法的核心。务必清楚 可能因为假失败而需要循环重试,而 则保证只有在值真正不匹配时才失败。
    4. 结合其他同步原语: 并非万能。在许多情况下,它需要与 、 甚至 / 等其他同步原语协同工作,以构建健壮的并发程序。
    5. 充分测试并发代码: 并发bug往往难以复现和调试。务必编写严格的并发测试,利用不同的线程数量、运行环境,甚至使用专门的并发测试工具(如 ThreadSanitizer),以发现潜在的数据竞争和死锁。
    6. 文档化内存序选择: 如果你使用了非默认的内存序,请务必在代码中详细注释,解释为什么选择这种内存序,它保证了什么,以及它不保证什么。这对于代码的维护和后续调试至关重要。

    总而言之, 是C++并发编程工具箱中的一把利器,它赋予了我们直接与硬件内存模型交互的能力,从而实现高性能的无锁编程。但伴随这种能力而来的,是对并发模型更深层次的理解和更严谨的设计。用得好,性能飞跃;用不好,bug缠身。

    以上就是C++如何使用std::atomic保证线程安全的详细内容,更多请关注php中文网其它相关文章!

  • IMAX背后的老头去世了 全好莱坞都欠他一句谢谢

    IMAX背后的老头去世了 全好莱坞都欠他一句谢谢

    imax背后的老头去世了 全好莱坞都欠他一句谢谢

    不过咱今天说这个,倒不是因为电影,而是IMAX技术的奠基人,大卫·基格利(David Keighley)去世了,老头享年78岁。

    该说不说,这个名字确实有点陌生,别说咱中国人,不少美国人估计也没听过。

    但是不少好莱坞明星和导演们,都转发新闻悼念了这老头,规格相当高。比如克里斯托弗·诺兰就说,如果没有这老头,好莱坞导演们就没法拍电影。

    维伦纽瓦也感叹说,有些人是不可替代的,就比如这老头。卡梅隆也说这是他真正的盟友。

    IMAX背后的老头去世了 全好莱坞都欠他一句谢谢

    IMAX背后的老头去世了 全好莱坞都欠他一句谢谢

    不过对咱们来说,虽然大家都没见过他,但你买的每一张IMAX电影票,背后都有这个老头给你提供服务。

    所以某种意义上,这也算是又一位离开咱们的老哥们了。

    但要仔细唠一下这老头到底干了啥,咱还是得从IMAX的发明说起。

    众所周知,拍电影一定要有摄像机。但是比起一般的电影,IMAX这玩意尤其不好拍,自打一开始就难。

    1967年的蒙特利尔世博会上,就有人用7台放映机编组,搞出来一个巨大的画面,也就是IMAX的雏形,这在当时可谓是轰动。但是这玩意缺点也明显,现在看着这效果,可以说是拉完了。

    IMAX背后的老头去世了 全好莱坞都欠他一句谢谢

    所以有人就琢磨,那这么着,我找一个PROPLUSMAX的摄影机,给它装上PROPLUSMAX的超大胶卷,不就能放出来超大屏电影了嘛,还更清晰。

    IMAX背后的老头去世了 全好莱坞都欠他一句谢谢

    这也就是IMAX用的70mm胶片,每一帧画面的长度占据15个齿孔,是普通版的3倍;一帧的成像面积达到了约70mm x 48.5mm,是普通35mm胶片面积的近10倍,主打一个大力出奇迹。

    而用来拍这种格式的摄影机,也是特制的IMAX 70mm胶片摄影机,极其笨重、噪音大且操作困难,但拍胶片没它还真不行,这种机器,到现在全世界一共也才8台。

    IMAX背后的老头去世了 全好莱坞都欠他一句谢谢

    因为这个胶片IMAX不是太难拍了嘛,于是IMAX搞了个亲民路线:DMR技术,(数字原底翻版技术),能把普通35mm胶片逐帧数字优化,转制成更高质量的IMAX版本,说白了就是美颜精修,靠算法提升清晰度。

    虽然这些拍摄跟大卫·基格利关系不大,但甭管你是扛胶片机拍,还是靠DMR转制,要从这些复杂的拍摄到进电影院,那其实都要过人家这一关。

    IMAX背后的老头去世了 全好莱坞都欠他一句谢谢

    这是怎么个事呢?主要跟电影制作有关系。

    拿DMR(数字原底翻版技术)影片来说,为了能把这35mm胶卷搞成IMAX画质,基格利得一帧一帧的分析原始素材,进行降噪、稳定画面、增强细节,还要跟导演一起重新调色,来适应IMAX银幕巨大的亮度动态范围。

    举个例子,比方普通银幕上的暗部看着还可以,但在IMAX的亮度下,啥细节都会一览无余,可能会暴露道具或布景的瑕疵。 这就得基格利来跟导演沟通,是压暗这些细节,还是用CG修补,怎么才能不穿帮。

    IMAX背后的老头去世了 全好莱坞都欠他一句谢谢

    再比如拍《黑暗骑士》时,诺兰首次大规模使用IMAX胶片摄影机拍叙事片段。这里面有一个很大的问题就是,如何让普通35mm胶片画面与IMAX胶片画面之间的衔接非常自然,还不影响震撼效果。

    基格利和他的团队为这事测试了几个月,他们调整了DMR转制部分的颗粒感和色彩饱和度,使其能与原生IMAX片段的质感进行“无缝衔接”,但又保留了画幅切换带来的视觉冲击力。

    靠着这种对细节的把控,才成就了那段经典的小丑登场戏份。

    好莱坞业内有句流传甚广的话,形容老头在DMR这方面的权威:“没有基格利的点头,任何IMAX拷贝都不能出厂。”

    IMAX背后的老头去世了 全好莱坞都欠他一句谢谢

    而在另一边,随着数码相机发展,数字IMAX摄影机也出现了。像《复仇者联盟3&4》这些影片就全程是数字IMAX拍的。

    但是IMAX发烧友里也有不少传统派,比如诺兰就觉得,只有IMAX胶片才有内味儿,毕竟你摄影机6-8K的分辨率也就顶天了,人家胶片机等效能达到18K。

    所以从《黑暗骑士》到《敦刻尔克》,诺兰也把胶片IMAX拍摄的比例从30%干到了70%。最近的新片《奥德赛》,更是要做到100%。

    IMAX背后的老头去世了 全好莱坞都欠他一句谢谢

    但是,不管你用胶片还是用数字,把它们搬进电影院才是最难的一步。

    全球有那么老些电影院,你怎么能让每个IMAX影院看到的效果,都跟你在审片房里看到的效果一致呢?

    要是影院掉链子,那前面全白干了。

    为此,基格利创立并执行了一套业内最严苛的影院质量认证体系——IMAX Quality Assurance

    IMAX背后的老头去世了 全好莱坞都欠他一句谢谢

    这套体系涵盖了你能想到的电影院里的一切,比如为了亮度均衡,他的团队会定期用数字光度计,测量银幕每个角落的亮度,来确保误差极小;还会用专业麦克风阵进行声学校准,确保每个声道的延迟、响应都完全一致;

    再比如画面上,哪怕是老式的胶片IMAX放映机,基格利的系统通过自动校准装置,将误差控制在人眼无法感知的范围内,而且IMAX会进行定期抽查和飞行检查,一旦发现影厅的灯泡亮度衰减、音响失准或银幕污损,会立刻要求停业整改。

    一句话,任何一家想挂上IMAX招牌的影院,都得通过这套严苛的认证。在这个体系里,基格利的标准就是法律。靠着这个“全球IMAX电影的纪委书记”,才确保了IMAX给全球观众的体验一致。

    IMAX背后的老头去世了 全好莱坞都欠他一句谢谢

    不过,大卫·基格利的工作,其实还远不止于技术纠错,他也是导演艺术意图的终极保障者。

    比如在美学上,传统宽银幕(2.39:1)更强调水平方向延展,而IMAX的1.43:1和1.90:1幕幅则带来了更高的银幕。这就让导演可以研究出更具纵深感的构图。

    像在《敦刻尔克》里,诺兰就经常把前景的士兵放在画面下方,而将天空与硝烟置于画面上缘。这种构图除了在观众眼前营造出强烈的的纵深感和空间压迫感,也对放映质量提出了最高的要求。

    任何一点亮度不足、对比度下降或清晰度损失,都会让导演精心设计瞬间瓦解。

    而正因为有基格利的存在,诺兰才敢于自信地使用这些极其考验放映质量的构图,所以胶片IMAX的第一粉头诺兰,给基格利写悼词才会那么情真意切,这是真爱过。

    IMAX背后的老头去世了 全好莱坞都欠他一句谢谢

    总之,基格利这老爷子既是IMAX的奠基人,也是电影艺术的把门人,虽然人已经走了,但他定下的规矩,已经刻进了IMAX的DNA里。把IMAX从一项牛逼的技术,变成了一种艺术奇迹。

    所以下一次,当你坐在IMAX影厅,感受那顶天立地的画面和澎湃的声浪将你完全吞没时,请记得:

    这份极致的沉浸感背后,曾有一个老头用他的一生,守护着眼前荧幕上的这场梦。

    以上就是IMAX背后的老头去世了 全好莱坞都欠他一句谢谢的详细内容,更多请关注php中文网其它相关文章!

  • 电影《731》中所有日本角色均由日籍演员饰演,导演:我不愿让中国人扮演日本人

    电影《731》中所有日本角色均由日籍演员饰演,导演:我不愿让中国人扮演日本人

    9月18日,备受关注的电影《731》上映。电影《731》中所有日本角色均由日籍演员饰演,这一细节赢得众多观众对创作者细节把控的赞赏。导演赵林山回应道,如今为电影全数选用日本演员并非难事。但《731》拍摄正值疫情期间,当时要从日本调集大量演员这一行为本身就近乎天方夜谭。

    电影《731》中所有日本角色均由日籍演员饰演,导演:我不愿让中国人扮演日本人 - php中文网

    “我们仍组建了一支80余人的日本演员阵容,如此坚持意义何在?”赵林山自问自答:“我不愿让中国人扮演日本人。因为日本民族具有鲜明的双面性,其独有的样式是外人难以演绎的,只有日本人自身才具备这种特质。”

    以上就是电影《731》中所有日本角色均由日籍演员饰演,导演:我不愿让中国人扮演日本人的详细内容,更多请关注php中文网其它相关文章!