精华内容
下载资源
问答
  • 函数inet_network():inet_networkinet_addr函数都是用于将字符串形式转换为整数形式用的,两者区别很小,inet_addr返回的整数形式是网络字节,而inet_network返回的整数形式是主机字节。 函数int inet_...


    注意:结构体之间不能直接进行强制转换, 必须先转换成指针类型才可以进行结构体间的类型转换, 这里需要明确的定义就是什么才叫强制转换.
    强制转换是将内存中一段代码以另一种不同类型的方式进行解读, 因此转换的空间必须与源空间一一对应.
    而结构体则是由不固定的多种类型变量组合而成, 因此强制转换时并不确定原格式与目标格式确定的对应关系, 例如一个结构体为3个变量, 而另一个则为2个, 那么就无法确定如何分配.
    因此最简单的让计算机可以理解的方式就是先将结构体转换成指针, 然后将指针转换成目标格式, 再读取转换后的指针, 这样计算机就会按照指针的格式读取特定目标代码段了.


     1、sockaddr 、sockaddr_in 、addrinfo比较

    struct sockaddr
    {
        unsigned  short  sa_family;     /* address family, AF_xxx */
        char  sa_data[14];                 /* 14 bytes of protocol address */
    };
    

    sa_family是地址家族,一般都是“AF_xxx”的形式。好像通常大多用的是都是AF_INET。
    sa_data是14字节协议地址。
    此数据结构用做bind、connect、recvfrom、sendto等函数的参数,指明地址信息。

    但一般编程中并不直接针对此数据结构操作,而是使用另一个与sockaddr等价的数据结构sockaddr_in,在netinet/in.h中定义:

    struct  sockaddr_in
    {
        short  int  sin_family;                      /* Address family */
        unsigned  short  int  sin_port;       /* Port number */
        struct  in_addr  sin_addr;              /* Internet address */
        unsigned  char  sin_zero[8];         /* Same size as struct sockaddr */
    };
    struct  in_addr
    {
        unsigned  long  s_addr;
    };
    
    typedef struct in_addr
    {
        union
        {
            struct
            {
                unsigned char s_b1,
                         s_b2,
                         s_b3,
                         s_b4;
            } S_un_b;
            struct
            {
                unsigned short s_w1,
                         s_w2;
            } S_un_w;
            unsigned long S_addr;
        } S_un;
    } IN_ADDR;
    
    sin_family 指代协议族,在socket编程中只能是AF_INET
    sin_port存储端口号(使用 网络字节顺序
    sin_addr 存储IP地址,使用in_addr这个数据结构
    sin_zero 是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节
    s_addr 按照 网络字节顺序 存储IP地址。

     

    addrinfo结构是getaddrinfo函数用来保存主机地址信息的结构体。在头文件Ws2tcpip.h中定义。

    typedef struct addrinfo
    {
        int ai_flags;
        int ai_family;
        int ai_socktype;
        int ai_protocol;
        size_t ai_addrlen;
        char* ai_canonname;
        struct sockaddr* ai_addr;
        struct addrinfo* ai_next;
    
    } ADDRINFOA,  *PADDRINFOA;
    

    ai_flags:表示getaddrinfo要使用的选项,可赋的值在Winsock2.h中有定义,且可以是几个值的组合。

    ai_family:地址族。一般为AF_INET

    ai_socktype:套接字类型。SOCK_STREAM,SOCK_DGRAM,SOCK_RAW,SOCK_RDM,SOCK_SEQPACKET

    ai_protocol:协议类型。可取的值取决于ai_address和ai_socktype的值。IPPROTO_TCP,IPPROTO_UDP,IPPROTO_RM。

    如果ai_family置为AF_IRDA,则ai_protocol必须为0。

    ai_addrlen:ai_addr指向的缓冲区的字节数。

    ai_canonname:主机的规范化名称(canonical name)。

    ai_addr:指向sockaddr结构的指针。

     

    2、IP地址的网络字节顺序和机器字节顺序

    210.25.132.181属于IP地址的ASCII表示法,也就是字符串形式。英语叫做IPv4 numbers-and-dots notation。

    如果把210.25.132.181转换为整数形式,是3524887733,这个就是整数形式的IP地址。英语叫做binary data。(其实binary是二进制的意思)

    Internet地址用“.”间隔的地址可有下列几种表达方式: a.b.c.d,a.b.c,a.b,a   当四个部分都有定值时,每个都解释成一个字节数据,从左到右组成Internet四字节地址。 Internet 地址以网络字节顺序返回(字节从左到右排列。请注意,当一个Internet地址在Intel机器上表示成一个32位整型数时,则上述的字节为“d.c.b.a”。这是因为Intel处理器的字节是从右向左排列的。 上述中的“a.b.c.d”就为网络字节顺序,而“d.c.b.a”则为机器字节顺序。

     

    3、ntohs, ntohl, htons,htonl的比较和详解

    ip地址是32位的,端口号是16位的 。

    ntohs =net to host short int 16

    htons=host to net short int 16

    ntohs =net to host long int 32

    htonl=host to net long int  32

     u_short PASCAL FAR ntohs( u_short netshort);

          netshort:一个以网络字节顺序表达的16位数。本函数将一个16位数由网络字节顺序转换为16位主机字节顺序。

     u_short PASCAL FAR htons( u_short hostshort);

           hostshort:一个主机字节顺序表达的16位数。 本函数将一个16位数从主机字节顺序转换成16位网络字节顺序。

     u_long PASCAL FAR htonl( u_long hostlong);  

           hostlong:一个主机字节顺序表达的32位数。本函数将一个32位数从主机字节顺序转换成32位网络字节顺序。

     u_long PASCAL FAR ntohl( u_long netlong)

           netlong:一个以网络字节顺序表达的32位数。 本函数将一个32位数由网络字节顺序转换为32位的主机字节顺序。

     

    4、 inet_network()、inet_addr()、inet_aton()、inet_ntoa()

    int inet_aton(const char *string, struct in_addr *addr);

    in_addr_t inet_addr(const char *cp);

    in_addr_t inet_network(const char *cp);

    char FAR* PASCAL FAR inet_ntoa( struct in_addr in);

    函数inet_addr():将IP地址从 点数格式转换成无符号长整型。使用方法如下:

    ina.sin_addr.s_addr = inet_addr("132.241.5.10");
    注意,inet_addr()返回的地址已经是网络字节格式,所以你无需再调用 函数htonl()。

    函数inet_network():inet_network和inet_addr函数都是用于将字符串形式转换为整数形式用的,两者区别很小,inet_addr返回的整数形式是网络字节序,而inet_network返回的整数形式是主机字节序。

    函数int inet_aton(const char *string, struct in_addr *addr):是一个改进的方法来将一个字符串IP地址转换为一个32位的网络序列IP地址。输入参数string包含ASCII表示的IP地址。  输出参数addr是将要用新的IP地址更新的结构。   返回值: 如果这个函数成功,函数的返回值非零。如果输入地址不正确则会返回零。使用这个函数并没有错误码存放在errno中,所以他的值会被忽略。 inet_aton函数和上面这俩小子的区别就是在于他认为255.255.255.255是有效的,他不会冤枉这个看似特殊的IP地址。inet_aton函数返回的是网络字节序的IP地址。如:inet_aton("127.0.0.1",&adr_inet.sin_addr))

    函数inet_ntoa()将网络地址转换成“.”点隔的字符串格式。本函数将一个用in参数所表示的Internet地址结构转换成以“.” 间隔的诸如“a.b.c.d”的字符串形式。请注意inet_ntoa()返回的字符串存放在WINDOWS套接口实现所分配的内存中。应用程序不应假设该内存是如何分配的。在同一个线程的下一个WINDOWS套接口调用前,数据将保证是有效。返回值:   若无错误发生,inet_ntoa()返回一个字符指针。否则的话,返回NULL。其中的数据应在下一个WINDOWS套接口调用前复制出来。函数 inet_ntoa()("ntoa"的含义是"network to ascii"),就像这样: printf("%s",inet_ntoa(ina.sin_addr)); 它将输出IP地址。

    5、inet_pton() 和inet_ntop()

         Linux下这2个IP地址转换函数,可以在将IP地址在“点分十进制”和“整数”之间转换   而且,inet_pton和inet_ntop这2个函数能够处理ipv4和ipv6。算是比较新的函数了。

    inet_pton函数原型如下[将“点分十进制” -> “整数”]   #include <sys/types.h>   #include <sys/socket.h>   #include <arpa/inet.h>   int inet_pton(int af, const char *src, void *dst);   这个函数转换字符串到网络地址,第一个参数af是地址族,转换后存在dst中 。

    inet_pton 是inet_addr的扩展,支持的多地址族有下列:   af = AF_INET   src为指向字符型的地址,即ASCII的地址的首地址(ddd.ddd.ddd.ddd格式的),函数将该地址   转换为in_addr的结构体,并复制在*dst中   af =AF_INET6   src为指向IPV6的地址,,函数将该地址   转换为in6_addr的结构体,并复制在*dst中   如果函数出错将返回一个负值,并将errno设置为EAFNOSUPPORT,如果参数af指定的地址族和src格式不对,函数将返回0。

        inet_ntop函数原型如下[将“点分十进制” -> “整数”]   #include <sys/types.h>   #include <sys/socket.h>   #include <arpa/inet.h>   const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt);   这个函数转换网络二进制结构到ASCII类型的地址,参数的作用和上面相同,只是多了一个参数socklen_t cnt,他是所指向缓存区dst的大小,避免溢出,如果缓存区太小无法存储地址的值,则返回一个空指针,并将errno置为ENOSPC。

    6、gethostbyname和gethostbyaddr

    这两个函数仅仅支持IPv4,getaddrinfo函数能够处理名字到地址以及服务到端口这两种转换,返回的是一个sockaddr结构的链表而不是一个地址清单。这些sockaddr结构随后可由套接口函数直接使用。如此以来,getaddrinfo函数把协议相关性安全隐藏在这个库函数内部。应用程序只要处理由getaddrinfo函数填写的套接口地址结构。该函数在 POSIX规范中定义了。

    #include<netdb.h>
    int getaddrinfo( const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result );
    返回0:  成功
    返回非0:  出错

    hostname:一个主机名或者地址串(IPv4的点分十进制串或者IPv6的16进制串)
    service:一个服务名或者10进制端口号数串。
    hints:可以是一个空指针,也可以是一个指向某个addrinfo结构的指针,调用者在这个结构中填入关于期望返回的信息类型的暗示。举例来说:如果指定的服务既支持TCP也支持UDP,那么调用者可以把hints结构中的ai_socktype成员设置成SOCK_DGRAM使得返回的仅仅是适用于数据报套接口的信息。

    本函数通过result指针参数返回一个指向addrinfo结构链表的指针,而addrinfo结构定义在头文件netdb.h中:
    struct addrinfo{
        int       ai_flags; 
        int       ai_family;
        int       ai_socktype;
        int       ai_protocol;
        socklen_t ai_addrlen;
        char    *ai_canonname;
        struct sockaddr *ai_addr;
        struct addrinfo *ai_next;
    };

    如果本函数返回成功,那么由result参数指向的变量已被填入一个指针,它指向的是由其中的ai_next成员串联起来的addrinfo结构链表。可以导致返回多个addrinfo结构的情形有以下2个:
        1.    如果与hostname参数关联的地址有多个,那么适用于所请求地址簇的每个地址都返回一个对应的结构。
        2.    如果service参数指定的服务支持多个套接口类型,那么每个套接口类型都可能返回一个对应的结构,具体取决于hints结构的ai_socktype成员。

    我们必须先分配一个hints结构,把它清零后填写需要的字段,再调用getaddrinfo然后遍历一个链表逐个尝试每个返回地址。

    getaddrinfo解决了把主机名和服务名转换成套接口地址结构的问题。

    其中,如果getaddrinfo出错,那么返回一个非0的错误值。


    展开全文
  • 也是可以的,原子量选择的内存序有关,我们把这个问题放到下一节专门研究。 上面已经看到atomic是一个模版,那么也就意味着我们可以把自定义类型变成原子变量。但是是否任意类型都可以定义为原子类型呢?当然...

    一、多线程下共享变量的问题

    在多线程编程中经常需要在不同线程之间共享一些变量,然而对于共享变量操作却经常造成一些莫名奇妙的错误,除非老老实实加锁对访问保护,否则经常出现一些(看起来)匪夷所思的情况。比如下面便是两种比较“喜闻乐见”的情况。

    (a) i++问题

    在多线程编程中,最常拿来举例的问题便是著名的i++ 问题,即:多个线程对同一个共享变量i执行i++ 操作。这样做之所以会出现问题的原因在于i++这个操作可以分为三个步骤:

    stepoperation
    1i->reg(读取i的值到寄存器)
    2inc-reg(在寄存器中自增i的值)
    3reg->i (写回内存中的i)

    上面三个步骤中间是可以间隔的,并非原子操作,也就是说多个线程同时执行的时候可能出步骤的交叉执行,例如下面的情况:

    stepthread Athread B
    1i->reg
    2inc-reg
    3i->reg
    4inc-reg
    5reg->i
    6reg->i

    假设i一开始为0,则执行完第4步后,在两个线程都认为寄存器中的值为1,然后在第5、6两步分别写回去。最终两个线程执行完成后i的值为1。但是实际上我们在两个线程中执行了i++,原本希望i的值为2。i++ 实际上可以代表多线程编程中由于操作不是原子的而引发的交叉执行这一类的问题,但是在这里我们先只关注对单个变量的操作。

    (b)指令重排问题

    有时候,我们会用一个变量作为标志位,当这个变量等于某个特定值的时候就进行某些操作。但是这样依然可能会有一些意想不到的坑,例如两个线程以如下顺序执行:

    stepthread Athread B
    1a = 1
    2flag= true
    3if flag== true
    4assert(a == 1)

    当B判断flag为true后,断言a为1,看起来的确是这样。那么一定是这样吗?可能不是,因为编译器和CPU都可能将指令进行重排(编译器不同等级的优化和CPU的乱序执行)。实际上的执行顺序可能变成这样:

    stepthread Athread B
    1flag = true
    2if flag== true
    3assert(a == 1)
    4a = 1

    这种重排有可能会导致一个线程内相互之间不存在依赖关系的指令交换执行顺序,以获得更高的执行效率。比如上面:flag 与 a 在A线程看起来是没有任何依赖关系,似乎执行顺序无关紧要。但问题在于B使用了flag作为是否读取a的依据,A的指令重排可能会导致step3
    的时候断言失败。

    解决方案

    一个比较稳妥的办法就是对于共享变量的访问进行加锁,加锁可以保证对临界区的互斥访问,例如第一种场景如果加锁后再执行i++ 然后解锁,则同一时刻只会有一个线程在执行i++ 操作。另外,加锁的内存语义能保证一个线程在释放锁前的写入操作一定能被之后加锁的线程所见(即有happens before 语义),可以避免第二种场景中读取到错误的值。

    那么如果觉得加锁操作过重太麻烦而不想加锁呢?C++11提供了一些原子变量与原子操作来支持。

    二、 C++11的原子量

    C++11标准在标准库atomic头文件提供了模版atomic<>来定义原子量:

    template< class T >
    struct atomic;

    它提供了一系列的成员函数用于实现对变量的原子操作,例如读操作load,写操作store,以及CAS操作compare_exchange_weak/compare_exchange_strong等。而对于大部分内建类型,C++11提供了一些特化:

    std::atomic_bool    std::atomic<bool>
    std::atomic_char    std::atomic<char>
    std::atomic_schar   std::atomic<signed char>
    std::atomic_uchar   std::atomic<unsigned char>
    std::atomic_short   std::atomic<short>
    std::atomic_ushort  std::atomic<unsigned short>
    std::atomic_int std::atomic<int>
    std::atomic_uint    std::atomic<unsigned int>
    std::atomic_long    std::atomic<long>
    ······
    //更多类型见:http://en.cppreference.com/w/cpp/atomic/atomic

    实际上这些特化就是相当于取了一个别名,本质上是同样的定义。而对于整形的特化而言,会有一些特殊的成员函数,例如原子加fetch_add
    、原子减fetch_sub、原子与fetch_and、原子或fetch_or等
    。常见操作符++、--、+=、&= 等也有对应的重载版本。

    接下来以int类型为例,解决我们的前面提到的i++ 场景中的问题。先定义一个int类型的原子量:

    std::atomic<int> i;

    由于int型的原子量重载了++ 操作符,所以i++ 是一个不可分割的原子操作,我们用多个线程执行i++ 操作来进行验证,测试代码如下:

    #include <iostream>
    #include <atomic>
    #include <vector>
    #include <functional>
    #include <thread>
    
    std::atomic<int> i;
    const int count = 100000;
    const int n = 10;
    
    void add()
    {
        for (int j = 0; j < count; ++j)
            i++;
    }
    
    int main()
    {
        i.store(0);
        std::vector<std::thread> workers;
        std::cout << "start " << n << " workers, "
                  << "every woker inc " << count  << " times" << std::endl;
    
        for (int j = 0; j < n; ++j)
            workers.push_back(std::move(std::thread(add)));
    
        for (auto & w : workers)
            w.join();
    
        std::cout << "workers end "
                  << "finally i is " << i << std::endl;
    
        if (i == n * count)
            std::cout << "i++ test passed!" << std::endl;
        else
            std::cout << "i++ test failed!" << std::endl;
    
        return 0;
    }

    在测试中,我们定义了一个原子量i,在main函数开始的时候初始化为0,然后启动10个线程,每个线程执行i++操作十万次,最终检查i的值是否正确。执行的最后结果如下:

    start 10 workers, every woker inc 100000 times
    workers end finally i is 1000000
    i++ test passed!

    上面我们可以看到,10个线程同时进行大量的自增操作,i的值依然正常。假如我们把i修改为一个普通的int变量,再次执行程序可以得到结果如下:

    start 10 workers, every woker inc 100000 times
    workers end finally i is 445227
    i++ test failed!

    显然,由于自增操作各个步骤的交叉执行,导致最后我们得到一个错误的结果。

    原子量可以解决i++问题,那么可以解决指令重排的问题吗?也是可以的,和原子量选择的内存序有关,我们把这个问题放到下一节专门研究。

    上面已经看到atomic是一个模版,那么也就意味着我们可以把自定义类型变成原子变量。但是是否任意类型都可以定义为原子类型呢?当然不是,cppreference中的描述是必须为TriviallyCopyable类型。这个连接为TriviallyCopyable的详细定义:

    http://en.cppreference.com/w/cpp/concept/TriviallyCopyable

    一个比较简单的判断标准就是这个类型可以用std::memcpy按位复制,例如下面的类:

    class {
        int x;
        int y;
    }

    这个类是一个TriviallyCopyable类型,然而如果给它加上一个虚函数:

    class {
        int x;
        int y;
        virtual int add ()
        {
            return x + y;
        }
    }

    这个类便不能按位拷贝了,不满足条件,不能进行原子化。

    如果一个类型能够满足atomic模版的要求,可以原子化,它就不用进行加锁操作了,因而速度更快吗?依然不是,atomic有一个成员函数is_lock_free,这个成员函数可以告诉我们到底这个类型的原子量是使用了原子CPU指令实现了无锁化,还是依然使用的加锁的方式来实现原子操作。不过不管是否用锁来实现,atomic的使用方式和表现出的语义都是没有区别的。具体用哪种方式实现C++标准并没有做约束(除了std::atomic_flag特化要求必须为lock free),跟平台有关。
    例如在我的Cygwin64、GCC7.3环境下执行如下代码:

    #include <iostream>
    #include <atomic>
    
    #define N 8
    
    struct A {
        char a[N];
    };
    
    int main()
    {
        std::atomic<A> a;
        std::cout << sizeof(A) << std::endl;
        std::cout << a.is_lock_free() << std::endl;
        return 0;
    }

    结果为:

    8
    1

    证明上面定义的类型A的原子量是无锁的。我在这个平台上进行了实验,修改N的大小,结果如下:

    Nsizeof(A)is_lock_free()
    111
    221
    330
    441
    550
    660
    770
    881
    > 8/0

    将A修改为内建类型,对于内建类型的实验结果如下:

    typesizeof()is_lock_free()
    char11
    short21
    int41
    long long81
    float41
    double81

    可以看出在我的平台下常用内建类型都是lock free的,自定义类型则和大小有关。

    从上面的统计还可以看出似乎当自定义类型的长度和某种自定义类型相等的时候is_lock_free()就为true。我推测可能我这里的atomic实现的无锁是通过编译器内建的原子操作实现的,只有当数据长度刚好能调用编译器内建原子操作时才能进行无锁化。查看GCC参考手册(https://gcc.gnu.org/onlinedocs/gcc-7.3.0/gcc/_005f_005fatomic-Builtins.html#g_t_005f_005fatomic-Builtins) 中内建原子操作的原型,以CAS操作为例:

    bool __atomic_compare_exchange_n (type *ptr, type *expected, type desired, bool weak, int success_memorder, int failure_memorder)
    
    bool __atomic_compare_exchange (type *ptr, type *expected, type *desired, bool weak, int success_memorder, int failure_memorder)

    其参数类型为type *的指针,在同一页可以找到GCC关于type的描述:

    The ‘__atomic’ builtins can be used with any integral scalar or pointer type that is 1, 2, 4, or 8 bytes in length. 16-byte integral types are also allowed if ‘__int128’ (see __int128) is supported by the architecture.

    type类型的长度应该为1、2、4、8字节中的一个,少数支持__int128的平台可以到16字节,所以只有长度为1,2,4,8字节的数据才能实现无锁。这个只是我的推测,具体是否如此尚不明白。

    三、C++11的六种内存序

    前面我们解决i++问题的时候已经使用过原子量的写操作load将原子量赋值,实际上成员函数还有另一个参数:

    void store( T desired, std::memory_order order = std::memory_order_seq_cst )

    这个参数代表了该操作使用的内存序,用于控制变量在不同线程见的顺序可见性问题,不只load,其他成员函数也带有该参数。c++11提供了六种内存序供选择,分别为:

    typedef enum memory_order {
        memory_order_relaxed,
        memory_order_consume,
        memory_order_acquire,
        memory_order_release,
        memory_order_acq_rel,
        memory_order_seq_cst
    } memory_order;

    之前在场景2中,因为指令的重排导致了意料之外的错误,通过使用原子变量并选择合适内存序,可以解决这个问题。下面先来看看这几种内存序

    memory_order_release/memory_order_acquire

    内存序选项用来作为原子量成员函数的参数,memory_order_release用于store操作,memory_order_acquire用于load操作,这里我们把使用了memory_order_release的调用称之为release操作。从逻辑上可以这样理解:release操作可以阻止这个调用之前的读写操作被重排到后面去,而acquire操作则可以保证调用之后的读写操作不会重排到前面来。听起来有种很绕的感觉,还是以一个例子来解释:假设flag为一个 atomic特化的bool 原子量,a为一个int变量,并且有如下时序的操作:

    stepthread Athread B
    1a = 1
    2flag.store(true, memory_order_release)
    3if( true == flag.load(memory_order_acquire))
    4assert(a == 1)

    实际上这就是把我们上文场景2中的flag变量换成了原子量,并用其成员函数进行读写。在这种情况下的逻辑顺序上,step1不会跑到step2后面去,step4不会跑到step3前面去。这样一来,实际上我们就已经保证了当读取到flag为true的时候a一定已经被写入为1了,场景2得到了解决。换一种比较严谨的描述方式可以总结为:

    • 对于同一个原子量,release操作前的写入,一定对随后acquire操作后的读取可见。

    这两种内存序是需要配对使用的,这也是将他们放在一起介绍的原因。还有一点需要注意的是:只有对同一个原子量进行操作才会有上面的保证,比如step3如果是读取了另一个原子量flag2,是不能保证读取到a的值为1的。

    memory_order_release/memory_order_consume

    memory_order_release还可以和memory_order_consume搭配使用。memory_order_release操作的作用没有变化,而memory_order_consume用于load操作,我们简称为consume操作,comsume操作防止在其后对原子变量有依赖的操作被重排到前面去。这种情况下:

    • 对于同一个原子变量,release操作所依赖的写入,一定对随后consume操作后依赖于该原子变量的操作可见。

    这个组合比上一种更宽松,comsume只阻止对这个原子量有依赖的操作重拍到前面去,而非像aquire一样全部阻止。将上面的例子稍加改造来展示这种内存序,假设flag为一个 atomic特化的bool 原子量,a为一个int变量,b、c各为一个bool变量,并且有如下时序的操作:

    stepthread Athread B
    1b = true
    2a = 1
    3flag.store(b, memory_order_release)
    4while (!(c = flag.load(memory_order_consume)))
    5assert(a == 1)
    6assert(c == true)
    7assert(b == true)

    step4使得c依赖于flag,当step4线程B读取到flag的值为true的时候,由于flag依赖于b,b在之前的写入是可见的,此时b一定为true,所以step6、step7的断言一定会成功。而且这种依赖关系具有传递性,假如b又依赖与另一个变量d,则d在之前的写入同样对step4之后的操作可见。那么a呢?很遗憾在这种内存序下a并不能得到保证,step5的断言可能会失败。

    memory_order_acq_rel

    这个选项看名字就很像release和acquire的结合体,实际上它的确兼具两者的特性。这个操作用于“读取-修改-写回”这一类既有读取又有修改的操作,例如CAS操作。可以将这个操作在内存序中的作用想象为将release操作和acquire操作捆在一起,因此任何读写操作的重拍都不能跨越这个调用。依然以一个例子来说明,flag为一个 atomic特化的bool 原子量,a、c各为一个int变量,b为一个bool变量,并且刚好按如下顺序执行:

    stepthread Athread B
    1a = 1
    2flag.store(true, memory_order_release)
    3b = true
    4c = 2
    5while (!flag.compare_exchange_weak(b, false, memory_order_acq_rel)) {b = true}
    6assert(a == 1)
    7if (true == flag.load(memory_order_acquire)
    8assert(c == 2)

    由于memory_order_acq_rel同时具有memory_order_release与memory_order_acquire的作用,因此step2可以和step5组合成上面提到的release/acquire组合,因此step6的断言一定会成功,而step5又可以和step7组成release/acquire组合,step8的断言同样一定会成功。

    memory_order_seq_cst

    这个内存序是各个成员函数的内存序默认选项,如果不选择内存序则默认使用memory_order_seq_cst。这是一个“美好”的选项,如果对原子变量的操作都是使用的memory_order_seq_cst内存序,则多线程行为相当于是这些操作都以一种特定顺序被一个线程执行,在哪个线程观察到的对这些原子量的操作都一样。同时,任何使用该选项的写操作都相当于release操作,任何读操作都相当于acquire操作,任何“读取-修改-写回”这一类的操作都相当于使用memory_order_acq_rel的操作。

    memory_order_relaxed

    这个选项如同其名字,比较松散,它仅仅只保证其成员函数操作本身是原子不可分割的,但是对于顺序性不做任何保证。

    代价

    总的来讲,越严格的内存序其性能开销会越大。对于我们常用的x86处理器而言,在处理器层级本身就支持release/acquire语义,因此release与acquire/consume都只影响编译器的优化,而memory_order_seq_cst还会影响处理器的指令重排。

    感想

    查资料学习原子量与内存序的过程中,深感多线程和并发的深奥,尽管出于好奇心会尝试了解各种内存序,但是在实践中写代码还是尽量选择比较稳妥的方式来实现吧,能加锁加锁,实在不行用默认的memory_order_seq_cst选项的原子量。毕竟就普通程序员而言,其实很难遇到要在这上面挤性能的场景,如果真觉得需要,多半是我们的设计不科学 = = !假如确确实实遇到这样的场景,做之前一定要谨慎的多做些研究,选择简单的使用方式,做到心中有数。

    转载于:https://www.cnblogs.com/FateTHarlaown/p/8919235.html

    展开全文
  • 【循渐进学Python——文件的备份重命名】 今天这篇博客就是教大家怎么对计算机中的文件进行操作,以前我们文件备份重命名的时候,总是复制粘贴或者是用鼠标去点,刚开始感觉这样还好. 但是如果工作量大了呢?用户...

    【循序渐进学Python——文件的备份和重命名】

    今天这篇博客就是教大家怎么对计算机中的文件进行操作,以前我们文件备份和重命名的时候,总是复制粘贴或者是用鼠标去点,刚开始感觉这样还好.

    但是如果工作量大了呢?用户需求高了呢?对我们来说就有点麻烦了,而使用Python对文件进行操作,就会方便很多,接下来我将向大家演示文件的操作,不要眨眼!来吧,展示!

    文件的备份

    备份流程:

    1.原始文件

    2.原始文件打开

    3.原始文件另存为

    4.新文件创建

    文件备份原理:

    1.先将原始文件打开

    2.将原始文件中的数据全部读取到内存变量中

    3.开启一个新的文件

    4.将变量中的数据全部写入到新的文件中

    案例:

    需求:实现文件的Copy

    思路:

    1. 假设Copy文件需要时间

    2. 用户选择输入需要Copy的文件路径及名称

    3. 同时用户选择输入需要Copy的目标路径

    4. 然后进行文件Copy

    5. 首先文件Copy之后需要能够正常打开运行

    6. 如果路径不存在则需要创建路径

    7. 如果指定的路径中已经有该文件则需要在原名称之后(1)(2)。。。。

    实现:

    # 导入包
    import os
    import time
    
    # 先提示用户输入要copy的文件路径以及名称.扩展名
    copyname = input("请输入要copy的文件路径:")
    name = input("请输入要copy的文件名称:")
    mblj = input("请输入要copy的目标路径:")
    # 以只读的方式打开要备份的文件
    oldwj = open(copyname + '//' + name, "rb")
    # 获取到文件的扩展名
    kzm = name.rfind('.')
    # 判断路径是否存在
    # 不存在创建
    if not os.path.exists(mblj):
        os.makedirs(mblj)
        os.chdir(mblj)
    else:
        # 存在的话修改默认目录
        os.chdir(mblj)
    # 判断扩展名是否存在
    if kzm > 0:
        fileext = name[kzm:]
    # copy以后的新文件名
    newname = name[:kzm] + "(1)" + fileext
    # 创建一个新文件并打开
    newwj = open(newname, "wb")
    # 将复制文件中的东西复制到新文件中
    while True:
        # 为了防止内存崩溃所以分批一点一点读取
        duq = oldwj.read(1024 * 1024 * 10)
        time.sleep(2)
        # 如果可以读取到数据的话,那么就继续
        # 如果不可以的话,那么就结束读取
        if duq:
            newwj.write(duq)
        else:
            break
    # copy完数据之后关闭文件
    oldwj.close()
    newwj.close()
    

    文件的相关操作

    • 经常对文件进行的操作:删除、重命名等一系列操作
    • 在python中这些功能都在os模块中,因此要是对文件进行操作首先导入os模块

    文件重命名

    • os模块中的rename()就可以完成对文件的重命名工作

    格式:

    rename(需要修改的文件的名称,新的文件的名称)
    

    实例:

    import os
    os.rename("123.txt", "456.txt")
    

    批量修改文件名

    先创建初始文件

    import os
    # 创建文件在Python文件夹里面创建10个
    # 检查python文件夹是否存在,如果不存在直接创建
    if not os.path.exists("python"):
        os.mkdir("python")
    # 检查完之后将默认路径设为python
    os.chdir("python")
    # 给该路径下创建10个文件
    for i in range(1, 11):
        file = open("【初学python第%d集】.txt" % i, "w")
        file.close()
    

    创建完以后再对初始文件进行批量修改文件名

    import os
    # 修改默认路径
    os.chdir("python")
    # 获取路径中所有的文件列表
    file_list = os.listdir()
    # 通过循环遍历目录列表
    for oldname in file_list:
        # 构造新的名字
        new_name = oldname.replace("初学", "循序渐进学")
        # 依次修改文件的名称
        os.rename(oldname, new_name)
    
    展开全文
  • 1.2 编 译 过 程 编 译 程 的 基 本 结 构 编译程序的功能是把用高级语言编写的源程序翻译成等价的机器语言或汇编语言表示的目标程序。 编译程序是将一种语言形式翻译成另一种语言形式,因此其工作过程一般可...

    1.2 编 译 过 程 和 编 译 程 序 的 基 本 结 构

    编译程序的功能是把用高级语言编写的源程序翻译成等价的机器语言或汇编语言表示的目标程序

    编译程序是将一种语言形式翻译成另一种语言形式,因此其工作过程一般可划分为下列 5 个阶段:词法分析、语法分析、语义分析及中间代码生成、代码优化和目标代码生成

    下面,我们以一个简单的程序段为例,分别介绍这 5 个阶段所完成的任务。
    例如,计算圆柱体表面积的程序段如下:
    float r , h , s ;
    s=23.1416r* ( h+r )

    第 1 阶段 词法分析
    词法分析阶段的任务是对构成源程序的字符串从左到右进行扫描和分解,根据语言的词法规则,识别出一个一个具有独立意义的单词(也称单词符号,简称符号)
    词法规则是单词符号的形成规则,它规定了哪些字符串构成一个单词符号。上述源程序
    通过词法分析识别出如下单词符号:
    基本字 float
    标识符 r , h ,s
    常数 3.1416 ,2
    运算符 * , +
    界符 ( ) ; , =

    第 2 阶段 语法分析
    语法分析的任务是在词法分析的基础上,根据语言的语法规则从单词符号串中识别出各种语法单位(如表达式、说明、语句等)并进行语法检查,即检查各种语法单位在语法结构上的正确性

    语言的语法规则规定了如何从单词符号形成语法单位,换言之,语法规则是语法单位的形成规则。
    上述源程序,通过语法分解,根据语言的语法规则识别单词符号串 s=23.1416r* ( h+r ),其中“ s ”是<变量>,单词符号串“ 23.1416r* ( h+r )”组合成<表达式>这样的语法单位,则由<变量> = <表达式>构成<赋值语句>这样的语法单位。在识别各类语法单位的同时进行语法检查,可以看到,上述源程序是一个语法上正确的程序。

    第 3 阶段 语义分析及中间代码生成
    定义一种语言除要求定义语法外,还要求定义其语义,即对语言的各种语法单位赋予具体的意义。语义分析的任务是首先对每种语法单位进行静态的语义审查,然后分析其含义,并用另一种语言形式(比源语言更接近于目标语言的一种中间代码或直接用目标语言)来描述这种语义

    例如,上述源程序中,赋值语句的语义为:计算赋值号右边表达式的值,并把它送到赋值号左边的变量所确定的内存单元中。语义分析时,先检查赋值号右边表达式和左边变量的类型是否一致,然后再根据赋值语句的语义,对它进行翻译可得到如下形
    式的四元式中间代码:
    ( 1 ) ( * , 2 , 3.1416 , T 1 )
    (2 ) ( * , T 1 , r , T 2 )
    (3 ) ( + , h , r , T 3 )
    (4 ) ( * , T 2 , T 3 , T 4 )
    (5 ) ( = , T 4 , —, s )
    其中,
    T 1 , T 2 , T 3 , T 4 是编译程序引进的临时变量,存放每条指令的运算结果。上述每一个四元式所表示的语义为:
    2 * 3.1416 ⇒ T 1
    T 1 * r ⇒ T 2
    h + r ⇒ T 3
    T 2 * T 3 ⇒ T 4
    T 4 ⇒ s
    这样,我们将源语言形式的赋值语句翻译为四元式表示的另一种语言形式,这两种语言在结构形式上是不同的,但在语义上是等价的。

    第 4 阶段 代码优化
    代码优化的任务是对前阶段产生的中间代码进行等价变换或改造,以期获得更为高效的,即省时间和空间的目标代码。优化主要包括局部优化和循环优化等,例如上述四元式经局部
    优化后得:
    ( 1 ) ( * , 6.28 , r , T 2 )
    (2 ) ( + , h , r , T 3 )
    (3 ) ( * , T 2 , T 3 , T 4 )
    (4 ) ( = , T 4 ,—, s )
    其中,2 和 3.1416 两个运算对象都是编译时的已知量,在编译时就可计算出它的值 6.28 ,而不必等到程序运行时再计算,即不必生成( * ,2 , 3.1416 , T 1 )的运算指令。

    第 5 阶段 目标代码生成
    目标代码生成的任务是将中间代码变换成特定机器上的绝对指令代码或可重定位的指令代码或汇编指令代码

    在编译程序的各个阶段中,都要涉及表格管理和错误处理。
    编译程序的重要功能之一,是记录源程序中所使用的变量的名字,并且收集与名字属性相关的各种信息。名字属性包括一个名字的存储分配、类型、作用域等信息。如果名字是一个函数名,还会包括其参数数量、类型、参数的传递方式以及返回类型等信息。符号表数据结构可以为变量名字创建记录条目,来登记源程序中所提供的或在编译过程中所产生的这些信息,编译程序在工作过程的各个阶段需要构造、查找、修改或存取有关表格中的信息,因此在编译程序中必须有一组管理各种表格的程序。

    如有侵权请联系删除。欢迎大家的关注。

    展开全文
  • 程序员在写应用层程序时,一般不需要考虑字节问题,因为字节跟操作系统硬件环境有关,而我们编写的程序要么不需要跨平台(比如只运行在windows),要么需要跨平台时会由Java这种跨平台语言在虚拟机层屏蔽掉了...
  • 全国大学生创新创业实践联盟-创新创业-按学习: all: 商业模式设计过程: 顶层设计,具体化设计,组织化设计。 商业模式评价准则: 1,客户价值实现的程度: 该模式能够在多大程度上实现创业团队原本拟定为...
  • 《循渐进Oracle:数据库管理、优化与备份恢复》在分析实例的过程中,兼顾深度与广度,不仅对实际问题的现象、产生原因和相关的原理进行了深入浅出的讲解,更主要的是,结合实际应用环境,提供了一系列 解决问题的...
  • hihocoder1067 DFS ST表
  • 章 第一章,使用SurfaceTexture作为Camera输出 第二章,使用TextureView渲染Camera画面 第三章,使用OpenGL为Camera添加各种滤镜 第四章,使用MediaCodec实现H264编码 第五章,使用MediaCodec编码AAC音频数据...
  • 字节

    2018-04-03 07:37:30
    字节 大于一个字节类型的数据在内存中的存放顺序(一个字节的...其实这种一般操作系统没有关系,而是计算机的硬件架构有关,具体的说,就是CPU的设计有关,英特尔的芯片AMD的芯片组都是使用的小端字节(...
  • 描述了在一个用例或操作的执行过程中对象如何通过消息相互交互,说明了消息如何在对象之间被发送接收以及发送的顺序。 二、特点 ⭐顺序图用来表示用例中的行为顺序。当执行一个用例行为时,顺序图中的每条消息...
  • 对于比特的理解

    千次阅读 2013-02-25 14:56:19
    比如,我们定义一个一个字节长的报文,包含两个域,每一个域刚好4个位,名字分别是a,b: 0 1 2 3 4 5 6 7 +-+-+-+-+-+-+-+-+ | a | b | +-+-+-+-+-+-+-+-+ 【D】 如果我们收到一个报文A,那么,...
  • POCO C++库学习分析 --

    万次阅读 2015-03-26 09:32:36
    POCO C++库学习分析 -- 1. POCO库概述: POCO是一个C++的开源库集。同一般的C++库相比,POCO的特点是提供了整一个应用框架。如果要做C++程序应用框架的快速开发,我觉得STL+boost+Poco+Qt+Mysql实在是个...
  • 谈谈字节

    2012-06-18 23:03:37
    谈谈字节 walterxia walter.xia@gmail.com  说到字节问题首先想到的是网络字节(Network byte order)。不同主机之间需要通信,可能各个主机的体系结构不同,其本身存放字节的顺序是不同的,X86体系一般...
  • 建模方法(四)-因子分析定义应用

    万次阅读 多人点赞 2018-08-20 20:58:05
    因子分析(factor analysis)也是一种降维、简化数据的技术。 它通过研究众多变量之间的内部依赖关系... 例如:商店的环境、商店 的服务商品的价格作为因子,这三个方面除了价格外,商店的环境 服务质量,都是客观...
  • ZAB协议:一个简单的全广播协议

    千次阅读 2016-05-13 09:24:17
    介绍 在雅虎,我们开发了一个高性能高可靠的分布式协调服务,叫做zookeeper,它允许大规模应用执行协调任务,比如:leader 推举、状态传播以及约会机制...我们发现这个服务在灵活性性能上都非常符合雅虎公司
  • winpcap教程 中文教程 ... 循渐进学习使用WINPCAP(一) ...这一部分展示了如何使用WINPCAP-API的不同的功能,它作为一个使用指南被划分为一系列的课时来带领读者循渐进的体会PCAP的程序设计的 魅力:从简单
  • 该文主要说说网络字节序和主机字节的区别以及Little endian与Big endian的概念。其实编程的事就比较简单了  我也懒得写了,直接引用了我觉得写的挺好的两篇文章: 什么是Big EndianLittle Endian? ...
  • 如何循渐进学习Python语言

    千次阅读 2017-05-06 18:15:17
    挺强大的书,如果你有什么不懂的东西,也许你可以从中搜索到相关的解释示例程序。所以一并推荐。    介绍了这么多书,我到底适合先读那本呢??下面介绍一下:  深入Python。阅读这个东西很轻松愉快。...
  • 原文来自:省钱版—-查找 IoT 设备TTL线 省钱版----查找 IoT 设备TTL线__未完待续 缘由 在IoT固件调试分析的过程中,建议首先在IoT设备的板子上焊接调试线,这是能够提高效率的一步。如果你的能力够高,直接...
  • 挺强大的书,如果你有什么不懂的东西,也许你可以从中搜索到相关的解释示例程序。所以一并推荐。    介绍了这么多书,我到底适合先读那本呢??下面介绍一下:  深入Python。阅读这个东西很轻松愉快。...
  • 字符编码方案的演变与字节 一、字符编码方案的演变 1. 根据前面的介绍,对于字符编码方案的演变,我们大致上可简单地划分为三个阶段:  ① ASCII编码方案阶段 → ② ANSI编码方案阶段 → ③ Unicode/UCS...
  • 指标可以是一个预定义指标的名字,也可以是一个用户定制的函数.指标函数应该返回单个张量,或一个完成metric_name - > metric_value映射的字典. 3、案例 01 基于多层感知器Softmax多分类(图片)  from ...
  • 把传过来的,之前转换过的数据文件名字变得规范点 set oracle_sid=xcldb rman target / nocatalog report schema; convert datafile 'c:\tmp\TBS_TRANS_5' db_file_name_convert 'c:\tmp\TBS_TRANS_5',...
  • 【Linux】循渐进学运维-服务篇-rsync配置文件

    千次阅读 热门讨论 2020-07-15 18:04:02
    大家好,我是高胜寒,本文是Linux运维-循渐进学运维-服务篇的第9篇文章 文章目录前言一. rsync配置文件举例二. rsync配置文件详解1. 配置文件的组成:2. 常见的全局参数1) port2) uid3) gid4) max connections5)...
  • 字符字节的区别 字节(byte):是一个8bit的存储单元,取值范围是0x00~0xFF。 字符(character):为语言意义上的一个符号,范围不一定。一个字符占用的字节数,随着编码方式的不同而不同。可能是一个字节,也可能是多个...
  • 字符编码方案的演变与字节     一、字符编码方案的演变 1. 前文已经提及,编号字符集CCS(简称字符集)与字符编码方式CEF(简称编码方式)这两个概念,在早期并没有必要严格区分。 在Unicode编码方案出现...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 57,451
精华内容 22,980
关键字:

和序有关的名字