【C#】volatile和内存屏障 笔记

原文:https://learn.microsoft.com/en-us/archive/msdn-magazine/2012/december/csharp-the-csharp-memory-model-in-theory-and-practice

半内存屏障

例如有这样的程序,Init()Print()在不同的线程上执行。

int data = 0;
bool inited = false; // 指示data字段是否已经设置的标志

void Init(){
    data = 42;
    inited = true; // 设置标志,指示data字段已经设置好了
}
void Print(){
    // 如果有inited的标志,打印data字段的值
    // 否则打印 Not initialized!
    if(inited){
        Console.WriteLine(data);
    }else{
        Console.WriteLine("Not initialized!");
    }
}
  • 一个线程执行Init(),在设置好data字段的值后,设置inited字段用来指示data的值已经设置好了。
  • 另一个线程执行Print(),在读取到inited字段为true时,data字段的值应该已经设置好了。

但是由于编译器、JIT和CPU都会重排内存操作指令来优化效率,所以可能会发生下面两种异常情况:

  1. Init()中,写入inited在写入data之前执行,即data字段还没写入值,却已经设置了inited的标志。
  2. Print()中,读取data在读取inited之前执行,就算读取到了inited为true,data的值也可能是未被设置的值(例如,data读取之后、inited读取之前,inited刚刚被设置为true)。

volatile关键字修饰的字段在被写入或被读取时,会加上内存屏障:

  • volatile write:具有release(发布,已完成写入)内存屏障,之前(上面)的语句不能被重排到之后(下面)。
  • volatile read:具有acquire(获取,准备读取)内存屏障,之后(下面)的语句不能被重排到之前(上面)。

现在:

int data = 0;
volatile bool inited = false; // 添加了volatile修饰

void Init(){
    data = 42;
    // 写入inited是volatile write,上面的语句不能被重排到下面,即data写入不会在inited写入之后执行
    inited = true;
}
void Print(){
    // 读取inited是volatile read,下面的语句不能被重排到上面,即data读取不会在inited读取之前执行。
    if(inited){
        Console.WriteLine(data);
    }else{
        Console.WriteLine("Not initialized!");
    }
}

因为内存屏障限制了指令重排,所以在写入inited的时候,data已经被写入好了;在读取inited的时候,data不会被提前读取。

全内存屏障

Thread.MemoryBarrier()会产生一个完全的内存屏障,即屏障之前的指令不能重排到屏障之后,屏障之后的指令也不能重排到屏障之前。

Interlocked的任何操作也会产生全内存屏障,之前和之后的任何指令都不会跨越Interlocked操作。

总结

这篇文章只是用来介绍内存屏障,一般写项目的时候最好不要用volatile
volatile是给标准库和接近CLR的底层代码使用的。
编写应用程序代码时,正确使用锁和Interlocked这类上层并发基元就能处理这些问题。

【JS/TS】函数this丢失问题

今天在写一个显示当前时间的网页,用了一个回调,来每秒更新时间。
但是实际运行时,时间并没有动态更新。

原因

仔细查了一下,原来是 Javascript 函数的 this 是在调用函数时确定的。

foo.call(this,args...);

观察一下函数的call函数的签名,可以发现第一个参数传入了 this 的值,函数运行时的 this 就是调用时传入的 this。
而回调函数被执行时,this已经不是我们原来预想的this了,所以变量没有被更改。

解决

那么如果要用回调函数,使用原来的this,该怎么办呢?
函数里提供了bind函数,可以用来内定函数调用时this的值和参数。

foo.bind(this,args...);

而在bind时写的参数,在执行时就会被插入到参数列表的最前面,可以用来内定前几个参数的值。
调用了bind之后,返回的是一个新的函数,它的this和前几个参数已经被内定死了。
之后函数被调用时无论传入的this是什么,实际执行时的this始终都是bind时内定好的this

【Android】Intent 启动 Activity

Intent 可以用于启动 Activity 或者在 Activity 间传递数据。

显式启动 Activity

// new Intent(Context context, Class class)
Intent intent = new Intent(this, TargetActivity.class);
startActivity(intent);

意图明确的 Intent,明确指出在 thiscontext 上启动 TargetActivity

隐式启动 Activity

首先在 AndroidManifest.xml 中 Activity 的 <intent-filter> 中定义 <action><category>

<activity android:name=".TargetActivity">
    <intent-filter>
        <!-- 可以有多个action和category -->
        <action android:name="com.example.test.ACTION_START"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="com.example.test.MY_CATEGORY"/>
    </intent-filter>
</activity>

【C++】移动语义

最近好好研究了一下 C++ 中的移动语义。

左值 & 右值

已经在内存中分配了地址的、可以取到地址的,是左值。
在内存中未分配地址的、仅存在于寄存器中无法取到地址的,是右值。

int a = 8; // a 已分配内存,是左值。
a + 1; // a + 1 仅存在于寄存器中,是右值。

右值引用

T&& 是右值引用,只接受右值

int&& a = 8; // 正确,8 仅存在于寄存器中。

int v = 9;
int&& b = v; // 错误,v 是左值,无法存入右值引用。
int&& c = v + 3; // 正确,v + 3 的结果仅存在于寄存器中,是右值,可以存入右值引用。

【C++】多线程与并发 1 - 概念入门

原文链接
原作者:Valentina Di Vincenzo
仅供个人学习、研究和欣赏

当我刚开始学 C++ 多线程的时候,我凌乱了。程序的复杂度如同一朵美丽的鲜花一样炸开了,并发的不确定行为要把我骨灰都给扬咯,当时我直接蒙了。如今你被我逮到了,我给你整个好活儿:这里是一份关于 C++ 并发和多线程的简易教程,放心,这个不会三天之内sa了你

现在,来快速复习一些基本概念,然后来雅间尝点并发代码。

概念

1. 什么是线程?

在进程被创建时,每个进程都有一个单独用来执行代码的线程,叫主线程。你可以向操作系统请求创建其他的线程,这些线程共享父进程的内存空间(代码区,数据区,其他操作系统资源,比如说打开的文件和信号)。另一方面,每个线程有各自的线程ID,栈,寄存器组和程序计数器。基本上来说,线程就是轻量的进程。在线程之间切换比进程快,线程间通信也比进程间通信更容易。