🚙

💨 💨 💨

×

  • Categories

  • Archives

  • Tags

  • About

GCC的原子操作函数

Posted on 04-11-2015 | In Linux

linux支持的哪些操作是具有原子特性的?知道这些东西是理解和设计无锁化编程算法的基础。

下面的东西整理自网络。先感谢大家的分享!

原子操作的api函数

gcc从4.1.2以后提供了 __sync_* 系列的下面几类的内嵌函数,提供用于针对数字或布尔型变量的原子操作。

n++类

这组返回更新前的值

type __sync_fetch_and_add (type *ptr, type value, ...)
type __sync_fetch_and_sub (type *ptr, type value, ...)
type __sync_fetch_and_or (type *ptr, type value, ...)
type __sync_fetch_and_and (type *ptr, type value, ...)
type __sync_fetch_and_xor (type *ptr, type value, ...)
type __sync_fetch_and_nand (type *ptr, type value, ...)

++n类

这组返回更新后的值

type __sync_add_and_fetch (type *ptr, type value, ...)
type __sync_sub_and_fetch (type *ptr, type value, ...)
type __sync_or_and_fetch (type *ptr, type value, ...)
type __sync_and_and_fetch (type *ptr, type value, ...)
type __sync_xor_and_fetch (type *ptr, type value, ...)
type __sync_nand_and_fetch (type *ptr, type value, ...)

type可以是1,2,4或8字节长度的int类型,即:

int8_t / uint8_t
int16_t / uint16_t
int32_t / uint32_t
int64_t / uint64_t

后面的可扩展参数(…)用来指出哪些变量需要memory barrier,因为目前gcc实现的是full barrier(类似于linux kernel 中的mb(),表示这个操作之前的所有内存操作不会被重排序到这个操作之后),所以可以略掉这个参数。

CAS类

CAS 即 compare-and-swap ,

下面这两个函数提供原子的比较和交换,如果 *ptr == oldval,就将 newval 写入 *ptr

  • 此函数在相等并写入的情况下返回 true

    bool __sync_bool_compare_and_swap (type *ptr, type oldval, type newval, ...)
    /* 对应的伪代码 */
    { if (*ptr == oldval) { *ptr = newval; return true; } else { return false; } }
    
  • 此函数在返回 oldval

    type __sync_val_compare_and_swap (type *ptr, type oldval, type newval, ...)
    /* 对应的伪代码 */
    { if (*ptr == oldval) { *ptr = newval; } return oldval; }
    

其他原子操作

type __sync_lock_test_and_set (type *ptr, type value, ...)

将 *ptr 设为value并返回 *ptr 操作之前的值。

void __sync_lock_release (type *ptr, ...)

将 *ptr 置 0

内存栅障

内存栅障主要是处理不同cpu运作时(主要是执行不同线程代码时),一个cpu对内存的操作的原子性,也就保证其他cpu见到的内存单元数据的正确性。

内存栅障介绍

如果有对某一变量上写锁, 就不能在不获得相应的锁时对其进行读取操作。
内存栅的作用在于保证内存操作的相对顺序, 但并不保证内存操作的严格时序。

内存栅并不保证 CPU 将本地快取缓存或存储缓冲的内容刷写回内存, 而是在锁释放时确保其所保护的数据, 对于能看到刚释放的那个锁的 CPU 或设备可见。
持有内存栅的 CPU 可以在其快取缓存或存储缓冲中将数据保持其所希望的、 任意长的时间, 但如果其它 CPU 在同一数据元上执行原子操作,
则第一个 CPU 必须保证, 其所更新的数据值, 以及内存栅所要求的任何其它操作, 对第二个 CPU 可见。

__sync_synchronize (...)

发出一个full barrier.

内存栅障应用

对于执行一条指令,操作到4个寄存器时,如:

write1(dev.register_size,size);
write1(dev.register_addr,addr);
write1(dev.register_cmd,READ);
write1(dev.register_control,GO);

最后一个寄存器是控制寄存器,在所有的参数都设置好之后向其发出指令,设备开始读取参数.

如果最后一条write1被换到了前几条语句之前,那么肯定不是我们所期望的,这时候我们可以在最后一条语句之前加入一个memory barrier,强制cpu执行完前面的写入以后再执行最后一条:

write1(dev.register_size,size);
write1(dev.register_addr,addr);
write1(dev.register_cmd,READ);
__sync_synchronize();
write1(dev.register_control,GO);

memory barrier有几种类型:

  • acquire barrier : 不允许将barrier之后的内存读取指令移到barrier之前(linux kernel中的wmb())。
  • release barrier : 不允许将barrier之前的内存读取指令移到barrier之后 (linux kernel中的rmb())。
  • full barrier : 以上两种barrier的合集(linux kernel中的mb())。

原子操作应用范围

原子操作只允许一次更新或读一个内存单元。 需要原子地更新多个单元时, 就必须使用锁来代替它了。

例如, 如果需要更新两个相互关联的计数器时, 就必须使用锁, 而不是两次单独的原子操作了。

原子操作例子

例子代码:

#include <stdio.h>  
#include <pthread.h>
#include <stdlib.h>

static int count = 0;

void *test_func(void *arg)
{
int i=0;
for(i=0;i<20000;++i){
__sync_fetch_and_add(&count,1);
}
return NULL;
}

int main(int argc, const char *argv[])
{
pthread_t id[20];
int i = 0;

for(i=0;i<20;++i){
pthread_create(&id[i],NULL,test_func,NULL);
}

for(i=0;i<20;++i){
pthread_join(id[i],NULL);
}
printf("%d\n",count);
return 0;
}

原子操作封装使用

根据常用的原子操作,封装一些实用的接口 :

//原子操作  
//设置值
int lock_set(volatile int &a, int value)
{
__sync_val_compare_and_swap(&a, a, value);
return a;
}
//加1
int lock_inc(volatile int &n)
{
return __sync_fetch_and_add(&n, 1);
}
//减1
int lock_dec(volatile int &n)
{
return __sync_fetch_and_sub(&n, 1);
}

//加值value
int lock_add(volatile int &n, int value)
{
return __sync_fetch_and_add(&n, value);
}

//减值value
int lock_sub(volatile int &n, int value)
{
return __sync_fetch_and_sub(&n, value);
}

//位或value
int lock_or(volatile int &n, int value)
{
return __sync_fetch_and_or(&n, value);
}

//位与value
int lock_and(volatile int &n, int value)
{
return __sync_fetch_and_and(&n, value);
}

//异或value
int lock_xor(volatile int &n, int value)
{
return __sync_fetch_and_xor(&n, value);
}

//无符号类型(函数重载)
//设置值
unsigned int lock_set(volatile unsigned int &a, unsigned int value)
{
__sync_val_compare_and_swap(&a, a, value);
return a;
}

//加1
unsigned int lock_inc(volatile unsigned int &n)
{
return __sync_fetch_and_add(&n, 1);
}

//减1
unsigned int lock_dec(volatile unsigned int &n)
{
return __sync_fetch_and_sub(&n, 1);
}

//加值value
unsigned int lock_add(volatile unsigned int &n, unsigned int value)
{
return __sync_fetch_and_add((int*)&n, value);
}

//减值value
unsigned int lock_sub(volatile unsigned int &n, unsigned int value)
{
return __sync_fetch_and_sub((int*)&n, value);
}

//位或value
unsigned int lock_or(volatile unsigned int &n, unsigned int value)
{
return __sync_fetch_and_or((int*)&n, value);
}

//位与value
unsigned int lock_and(volatile unsigned int &n, unsigned int value)
{
return __sync_fetch_and_and((int*)&n, value);
}

//异或value
unsigned int lock_xor(volatile unsigned int &n, unsigned int value)
{
return __sync_fetch_and_xor((int*)&n, value);
}

智能指针笔记

Posted on 04-11-2015 | In Misc

有些错误是编译器查不到的, 这种错误是最可怕的, 当项目大了之后,
即使用 Valgrind 也很难定位,
因为裸指针在团队合作中使用很容易导致其他成员忘记释放或多次释放, 所以在团队合作中一般使用智能指针.

而智能指针用的不好, 结果可能适得其反.

所以我们聊一下智能指针的几点注意事项.

总结自 C++ Primer.

一个简单的包含删除器的例子演示

#include<iostream>
#include<functional>
#include<memory>

using std::cout;
using std::endl;
using std::bind;
using namespace std::placeholders;

int testBind( int* a, int b, int c )
{
cout << *a + b + c << endl;
return *a;
}

struct Foo
{
Foo() = default;
Foo( const Foo & a )
{
data = a.data;
std::cout << "复制构造" << std::endl;
}
void print_sum( int n1, int n2 )
{
std::cout << n1 + n2 << '\n';
}
int data = 10;
};

int main()
{
//绑定类成员函数用对象的指针
Foo foo;
auto f3 = std::bind( &Foo::print_sum, &foo, 95, _1 );
f3( 5 );

auto check_testBind = std::bind( testBind, std::placeholders::_1, 3, 9 );
int * p = new int( 7 );
cout << check_testBind( p ) << endl;

shared_ptr<int> pi( new int(),
check_testBind );
*pi = 88;

shared_ptr<int> pii( new int( 12 ),
std::bind( testBind, std::placeholders::_1, 32, 19 ) );

std::function< int( int* ) > test_function_bind =
std::bind( testBind, std::placeholders::_1, 331, 9 );

cout << "test_function_bind( p, 331, 9 ) = " << test_function_bind( p ) << endl;;

shared_ptr<int> piii( new int( 112 ),
test_function_bind );

return 0;
}

打印结果 :

100
119
7
347
test_function_bind( p, 331, 9 ) = 7
452
63
100
请按任意键继续. . .

智能指针陷阱

智能指针可以提供对动态分配的内存安全而又方便的管理,但这建立在正确使用的
前提下 。 为了正确使用智能指针,我们必须坚持一些基本规范 :

  • 不使用相同的内置指针值初始化(或 reset) 多个智能指针 。
  • 不 delete get ( ) 返 回的指针 。
  • 不使用 get () 初始化或 reset 另 一 个智能指针 。
  • 如果你使用 get () 返 回的指针,记住当最后一个对应的智能指针销 毁 后, 你 的
    指 针就 变为无 效 了 。
  • 如果你使用智能指针管理的资源不是 new 分配的内存 , 记住传递给它一个删除
    器( 参见 12. 1.4 节 , 第 415 页和 12. 1.5 节 , 第 419 页)。

尽量用make_shared而非new

shared_ptr 可以协调对象的析构 , 但这仅限于其自身的拷贝 ( 也 是 shared_ptr)
之间。

这也是为什么我们推荐使用 make_shared 而不是 new 的原因 。

这样 , 我们就能在分配对象的同时就将 shared_ptr 与之绑定,
从而避免了无意中将同一块内存绑定到多个独立创建的 shared_ptr 上 。(这是最容易犯的错)

总结 : 所以我们要尽量一开始就用make_shared来分配动态内存, 而不是先new一个出来, 再找机会将它转为智能指针.

考虑下面对 shared_ptr 进行操作的函数 :

// 在函数被调用时 ptr 被创建并初始化
void process(shared_ptr<int> ptr)
{
// 使用 ptr
} // ptr 离 开作用域,被销毁

process 的参数是传值方式传递的,因此实参会被拷贝到 ptr 巾 。 拷贝 一 个 shared_ptr
会递增其引用讨数,因此, 在 process 运行过程中,引用七| 数值至少为 2 。 当 process
结束时, ptr 的引用计数会边喊,但不会变为 0 。 因此,当用音11变 11 ptr 被销毁时, ptr
指向的内存不会被释放 。

使用此函数的正确方法是传递给它一个 shared_ptr :

shared_ptr<int> p(new int(42)) ; // 引用计数为 1
process(p); // 拷贝 p 会递增它的引用计数 ;在 process 中引用计数位为 2
int i = *p; // 正确:引用计数位为 1

虽然不能传递给 process 一 个内置指针,但可以传递 给它 一 个(临时的)
shared_ptr , 这个 shared_ptr 是用 一个内 置指针显式构造的 。 但是,这样做很可能
会导致错误 :

int *x(new int(1024)); // 危险 x 是一个普通指针,不是一个智能指针
process(x) ; // 错误 : 不能将 int* 转换为 一个 shared_ptr<int>
process(shared_ptr<int>(x)); // 合法的,但内存会被释放!
int j = *x ; //未定义的 x 是一个空悬指针!

在上面的调用中 , 我们将一个临时 shared_ptr 传递给 process 。 当这个调用所在的表
达式结束时,这个临时对象就被销毁了 。 销毁这个临时变量会递减引用计数,此时引用计
数就变为 0 了 。 因此,当临时对象被销毁时 , 它所指向的内存会被释放 。
但 x 继续指 向 (已经释放的)内存,从而变成一个空悬指针。如果试图使用 x 的值,
其行为是未定义的 。

当将一个 shared_ptr 绑定到一个普通指针时 , 我们就将内存的管理责任交给了这
个 shared_ptr 一旦这样做 了 , 我们就不应该再使用内置指针来访问 shared_ptr 所
指向的内存了

不要使用 get 初始化另一个智能指针或为智能指针赋值

智能指针类型定义了 一个名为 get 的函数(参见表 1 2. J ),它返回一个内置指针,
指向智能指针管理的对象 。 此函数是为了这样一种情况而设计的 : 我们需要向不能使用智
能指针的代码传递一个内置指针。使用 get 返回的指告| 的代码不能 delete 此指针 。
虽然编译器不会给出错误信息 , 但将另一个智能指针也绑定到 get 返回的指针上是
错误的 :

shared_ptr<int> p(new int(42)) ; // 引 用计数为 1
int *q = p . get() ; // 正确 · 但使用 q 时妥注意,不要让它管理的指针被释放
{ // 新程序块
// 未定义:两个独立的 shared_ptr 指 向 相同的内存
shared ptr<int> (q) ;
} // 程序块结束, q 被销毁 , 它指向的内存被待放
int foo = *p ; // 未定义 p 指向的内存 已 经被释放了

在本例中, p 和 q 指 向相同的内存。由于它们是相互独立创建的,因此各自的引用计数都
是 1。 当 q 所在的程序块结束时 , q 被销毁 , 这会导致 q 指向的内存被释放 。 从而 p 变成
一个空悬指针,意味着当我们试图使用 p 时,将发生未定义的行为 。 而且 , 当 p 被销毁时 ,
这块内存会被第二次 delete 。

get 用来将指针的访问权限传递给代码,你只有在确定代码不会 get.
特别是,永远不要用 get 初始化另一个智能指针 del ete 指针或者为另一个智能指针赋值.

linux一些不要想当然的事(一)之目录权限

Posted on 03-18-2015 | In Linux

目录的可读/可写/可执行权限

不要把目录的这几个权限和档案的这几个权限混淆了, 不要想当然的以为是差不多的, 差很多!
记忆技巧 : 档案的rwx是针对于档案的内容来设计的, 而目录的rwx是针对于目录的文件名列表来设计的

. . .

Linux常用运维命令(df和free和top)笔记整理(三)

Posted on 03-11-2015 | In Linux

df

df命令用于显示磁盘分区上的可使用的磁盘空间。默认显示单位为KB。可以利用该命令来获取硬盘被占用了多少空间,目前还剩下多少空间等信息。

  • -a或–all:包含全部的文件系统;
  • –block-size=<区块大小>:以指定的区块大小来显示区块数目;
  • -h或–human-readable:以可读性较高的方式来显示信息;
  • -H或–si:与-h参数相同,但在计算时是以1000 Bytes为换算单位而非1024 Bytes;
  • -i或–inodes:显示inode的信息;
  • -k或–kilobytes:指定区块大小为1024字节;
  • -l或–local:仅显示本地端的文件系统;
  • -m或–megabytes:指定区块大小为1048576字节;
  • –no-sync:在取得磁盘使用信息前,不要执行sync指令,此为预设值;
  • -P或–portability:使用POSIX的输出格式;
  • –sync:在取得磁盘使用信息前,先执行sync指令;
  • -t<文件系统类型>或–type=<文件系统类型>:仅显示指定文件系统类型的磁盘信息;
  • -T或–print-type:显示文件系统的类型;
  • -x<文件系统类型>或–exclude-type=<文件系统类型>:不要显示指定文件系统类型的磁盘信息;
  • –help:显示帮助;
  • –version:显示版本信息

. . .

Linux常用运维命令(netstat和lsof)笔记整理(二)

Posted on 03-09-2015 | In Linux

netstat

netstat命令用来打印Linux中网络系统的状态信息,可让你得知整个Linux系统的网络情况。

  • -a或–all:显示所有连线中的Socket;
  • -A<网络类型>或–<网络类型>:列出该网络类型连线中的相关地址;
  • -c或–continuous:持续列出网络状态;
  • -C或–cache:显示路由器配置的快取信息;
  • -e或–extend:显示网络其他相关信息;
  • -F或–fib:显示FIB;
  • -g或–groups:显示多重广播功能群组组员名单;
  • -h或–help:在线帮助;
  • -i或–interfaces:显示网络界面信息表单;
  • -l或–listening:显示监控中的服务器的Socket;
  • -M或–masquerade:显示伪装的网络连线;
  • -n或–numeric:直接使用ip地址,而不通过域名服务器;
  • -N或–netlink或–symbolic:显示网络硬件外围设备的符号连接名称;
  • -o或–timers:显示计时器;
  • -p或–programs:显示正在使用Socket的程序识别码和程序名称;
  • -r或–route:显示Routing Table;
  • -s或–statistice:显示网络工作信息统计表;
  • -t或–tcp:显示TCP传输协议的连线状况;
  • -u或–udp:显示UDP传输协议的连线状况;
  • -v或–verbose:显示指令执行过程;
  • -V或–version:显示版本信息;
  • -w或–raw:显示RAW传输协议的连线状况;
  • -x或–unix:此参数的效果和指定”-A unix”参数相同;
  • –ip或–inet:此参数的效果和指定”-A inet”参数相同。

. . .

Linux常用运维命令(iostat)笔记整理(一)

Posted on 03-07-2015 | In Linux

在linux服务器开发过程中, 经常需要各种命令配合来查看各种状态,所以整理了一些老的笔记来备忘。

iostat

iostat主要用于监控系统设备的IO负载情况,iostat首次运行时显示自系统启动开始的各项统计信息,之后运行iostat将显示自上次运行该命令以后的统计信息。用户可以通过指定统计的次数和时间来获得所需的统计信息

  • -c 仅显示CPU统计信息.与-d选项互斥.
  • -d 仅显示磁盘统计信息.与-c选项互斥.
  • -k 以K为单位显示每秒的磁盘请求数,默认单位块.
  • -t 在输出数据时,打印搜集数据的时间.
  • -V 打印版本号和帮助信息.
  • -x 输出扩展信息.

. . .

python的struct模块

Posted on 03-02-2015 | In Misc

struct, 这玩意c/c++也有, 顾名思义, 能联想到这玩意是啥了

模块的主要作用就是对python基本类型值与

用python字符串格式表示的C struct类型间

的转化(This module performs conversions between Python values and C structs represented as Python strings.)

基本用法

import struct
import binascii
values = (1, 'abc', 2.7)
s = struct.Struct('I3sf')
packed_data = s.pack(*values)
unpacked_data = s.unpack(packed_data)

print 'Original values:', values
print 'Format string :', s.format
print 'Uses :', s.size, 'bytes'
print 'Packed Value :', binascii.hexlify(packed_data)
print 'Unpacked Type :', type(unpacked_data), ' Value:', unpacked_data

输出为:

Original values: (1, 'abc', 2.7) 
Format string : I3sf
Uses : 12 bytes
Packed Value : 0100000061626300cdcc2c40
Unpacked Type : <type 'tuple'> Value: (1, 'abc', 2.700000047683716)

代码中,

首先定义了一个元组数据,

包含int、string、float三种数据类型,

然后定义了struct对象,并制定了format‘I3sf’,

  • I 表示int,

  • 3s表示三个字符长度的字符串,

  • f 表示 float。最后通过struct的pack和unpack进行打包和解包。通过输出结果可以发现,

value被pack之后,

转化为了一段二进制字节串,

而unpack可以把该字节串再转换回一个元组,

但是值得注意的是对于float的精度发生了改变,

这是由一些比如操作系统等客观因素所决定的。打包之后的数据所占用的字节数与C语言中的struct十分相似。定义format可以参照官方api提供的对照表:

字节序设置

另一方面,打包的后的字节顺序默认上是由操作系统的决定的,

当然struct模块也提供了自定义字节顺序的功能,

可以指定大端存储、小端存储等特定的字节顺序,

对于底层通信的字节顺序是十分重要的,

不同的字节顺序和存储方式也会导致字节大小的不同。在format字符串前面加上特定的符号即可以表示不同的字节顺序存储方式,

例如采用小端存储 s = struct.Struct(‘<I3sf’)就可以了。官方api library 也提供了相应的对照列表:

MySQL入门二之一些小注意点

Posted on 03-02-2015 | In DB

distinct关键字

distinct是应用于所有列的, 而不是某一个列

mysql> select * from test_table;
+------+------+
| one | two |
+------+------+
| 56 | 12 |
| 52 | 10 |
| 56 | 12 |
| 56 | 13 |
+------+------+
4 rows in set (0.00 sec)

mysql> select distinct one, two from test_table;
+------+------+
| one | two |
+------+------+
| 56 | 12 |
| 52 | 10 |
| 56 | 13 |
+------+------+
3 rows in set (0.00 sec)

mysql> select distinct one from test_table;
+------+
| one |
+------+
| 56 |
| 52 |
+------+
2 rows in set (0.00 sec)

. . .

1…22232425262728293031323334353637
Mike

Mike

🚙 🚗 💨 💨 If you want to create a blog like this, just follow my open-source project, "hexo-theme-neo", click the GitHub button below and check it out ^_^ . It is recommended to use Chrome, Safari, or Edge to read this blog since this blog was developed on Edge (Chromium kernel version) and tested on Safari.

11 categories
290 posts
111 tags
about
GitHub Spotify
© 2013 - 2025 Mike