-
css flexbox 总结
引言
本文主要对css flexbox的关键点了总结,方便以后在以后的查阅。
容器的属性
对于容器需要指定其显示方式为flexbox
display: flex;
指定flex排列的方向、在排列时是否会换行,使用
flex-flow
可以快速设置二者属性flex-direction: row | row-reverse | column | column-reverse; flex-wrap: nowrap | wrap | wrap-reverse; flex-flow: <'flex-direction'> || <'flex-wrap'>;
用于调整主轴方向的排布(对于
row
来说就是横向,对于column
来说就是纵向)justify-content: flex-start | flex-end | center | space-between | space-around | space-evenly;
用于调整相交轴方向的行间排布(对于
row
来说就是纵向,对于column
来说就是横向)align-content: flex-start | flex-end | center | stretch | space-between | space-around;
用于调整相交交轴方向的单行对齐方式。需要注意的是其中
center
和baseline
的区别: 二者都表示居中,而baseline
会保证所有文字的底边处在同一条线上。align-items: flex-start | flex-end | center | baseline | stretch;
条目的属性
用于调整顺序
order: <integer>; /* default 0 */
用于调整每个条目的伸展程度
flex-grow: <number>; /* default 0 */
用于调整每个条目的缩小程度
flex-shrink: <number>; /* default 1 */
用于调整每个条目的默认尺寸
flex-basis: <length> | auto; /* default auto */
设置flex属性,排列顺序为
flex-grow
,flex-shrink
,flex-basis
flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ];
用于重载容器的
align-items
的设置align-self: auto | flex-start | flex-end | center | baseline | stretch;
-
emacs的键盘宏(keyboard macro)
对于一些有规律且重复性的编辑任务, 手动完成十分无聊, 并且需要耗费较长的时间。我在youtube上看到一个使用keyboard marco的 视频 后, 受到很大的启发, 在以后的使用中也会尝试使用宏。我总结了一下视频中的技巧要点,并查阅资料对相关知识点进行了补充和完善。
有梯子的同学可以去看看,视频地址:https://youtu.be/wFCO__0prCM
操作指令
开始记录宏: 命令名称kmacro-start-macro
, 快捷键C-x-(
或<f3>
结束记录宏: 命令名称kmacro-end-macro
, 快捷键C-x-)
或<f4>
执行宏: 命令名称kmacro-end-and-call-macro
, 快捷键C-x-e
, 可以使用C-u
指定这个宏的执行次数
清除多余的空格: 命令名称fixup-whitespace
, 这个命令我是第一次见到, 以后可以尝试多用用。参考资料
-
每周算法:最长对称子串
Description
Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.
Example 1:
Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.Example 2:
Input: "cbbd"
Output: "bb"Solution
我想到了两种方法:暴力解法、从中心展开
Approach 1 暴力解法
暴击解法的时间复杂度为
O(n3)
, 找出所有子串的时间复杂度为O(n2)
, 判断一个子串的时间复杂度为O(n)
; 空间复杂度为O(1)
。需要注意的是,如果子串过短,就没有必要进行对称性判断了。
下面是我的代码
bool isPalindrome(const string& str) { int len = str.length(); if (len <=0) { return false; } int head = 0; int tail = len - 1; while (head < tail) { if (str[head] != str[tail]) { return false; } ++head; --tail; } return true; } string longestPalindrome(string s) { string ans = ""; for (size_t i=0; i < s.length(); ++i) { for (size_t j=s.length()-i; j != 0; --j) { if (ans.length() >= j) { continue; } string temp = s.substr(i, j); if (ans.length() < temp.length() && isPalindrome(temp)) { ans = temp; } } } return ans; }
Approach 2 从中心展开
从中心展开方法的时间复杂度为
O(n2)
, 空间复杂度为O(1)
。需要注意的是坐标的计算,这个在字符串处理题目中是十分关键的,也是很容易出错的。
由于单个字符和两个相同字符都可以作为中心,这点需要额外注意一下。下面就是我的解法,使用的C++做的。
string longestPalindrome(string s) { string ans = ""; for (size_t i=0; i < s.length(); ++i) { // 如何确定初始的边界很重要 size_t j = i; size_t k = i; // 向两边拓展边界 while (j-1>=0 && s[j-1]==s[i]) { --j; } while (k+1<s.length() && s[k+1]==s[i]) { ++k; } while (j-1>=0 && k+1<s.length() && s[j-1]==s[k+1]) { --j; ++k; } if (k-j+1 > ans.length()) { ans = s.substr(j, k-j+1); } } return ans; }
leetcode上还有一个解法,使用java完成的,它的坐标计算也很有技巧性。
public String longestPalindrome(String s) { if (s == null || s.length() < 1) return ""; int start = 0, end = 0; for (int i = 0; i < s.length(); i++) { int len1 = expandAroundCenter(s, i, i); int len2 = expandAroundCenter(s, i, i + 1); int len = Math.max(len1, len2); if (len > end - start) { start = i - (len - 1) / 2; end = i + len / 2; } } return s.substring(start, end + 1); } private int expandAroundCenter(String s, int left, int right) { int L = left, R = right; while (L >= 0 && R < s.length() && s.charAt(L) == s.charAt(R)) { L--; R++; } return R - L - 1; }
Approach 3 动态规划(dynamic programming)
leetcode上还给给出了使用DP解决这个问题的方法。
我在leetcode上的discuss上找了个java写的解法。动态规划的时间复杂度为
O(n2)
, 空间复杂度为O(n2)
。
我对dp算法的了解还不多,个人感觉值得思考的是i
和j
的变化起点和变化方向。public String longestPalindrome(String s) { int n = s.length(); String res = null; boolean[][] dp = new boolean[n][n]; for (int i = n - 1; i >= 0; i--) { for (int j = i; j < n; j++) { dp[i][j] = s.charAt(i) == s.charAt(j) && (j - i < 3 || dp[i + 1][j - 1]); if (dp[i][j] && (res == null || j - i + 1 > res.length())) { res = s.substring(i, j + 1); } } } return res; }
Approach 4 Manacher算法
这个算法思路实在是新奇,感兴趣的同学可以 去看看 。
-
每周算法:最长不含重复字符的子串
Description
Given a string, find the length of the longest substring without repeating characters.
Examples:
Given "abcabcbb", the answer is "abc", which the length is 3.
Given "bbbbb", the answer is "b", with the length of 1.
Given "pwwkew", the answer is "wke", with the length of 3. Note that the answer must be a substring, "pwke" is a subsequence and not a substring.来源:LeetCode 03 Longest Substring Without Repeating Characters
Solution
好久没刷算法题了,手有点生,没有什么思路,下面的答案是看过提示之后才写出来的。
Approach 1 暴力解法
将所有的子串都穷举出来,并对它们进行判断,就能够得到最长子串的长度。这个算法很显然需要耗费很长时间。
需要注意的是: C++20中才支持std::set::contains
这个接口(根据cppreference.com)。bool isStringWithUniqueString(const string& str) { // 用于判断字符串是否是具有唯一字符的 set<char> setChar; for (size_t i = 0; i<str.length(); ++i) { if (setChar.find(str[i]) != setChar.end()) { return false; } setChar.insert(str[i]); } return true; } int lengthOfLongestSubstring(string s) { int lengthMax = 0; for (size_t i=0; i<s.length(); ++i) { for (size_t j=i+1; j<=s.length(); ++j) { // get sub-string string strTemp = s.substr(i, j-i); if (isStringWithUniqueString(strTemp)) { lengthMax = max(lengthMax, (int)strTemp.length()); } } } return lengthMax; }
Approach 2 滑动窗口
滑动窗口的概念在字符串处理问题中十分常用,保持子串的左端点不动,不断拓展右侧端点,就能穷举出所有满足条件的子串。
需要注意的是这句话:lengthMax = max(lengthMax, int(j-i+1));
-
std::max
并不能同时匹配int
和size_t
,所以需要进行强制类型转换。
- 在完成第
j
个字母的验证后,说明包含该字母的子串也满足条件,所以要进行+1
操作,这个主要是针对只有一个字母的字符串。
int lengthOfLongestSubstring(string s) { int lengthMax = 0; for (size_t i=0; i<s.length(); ++i) { // 这个循环目的是,验证从i到j的字符串是否满足条件 set<char> setChar; for (size_t j=i; j<s.length(); ++j) { char ch = s[j]; if (setChar.find(ch) != setChar.end()) { break; } else { lengthMax = max(lengthMax, int(j - i + 1)); } setChar.insert(ch); } } return lengthMax; }
Approach 3 优化的滑动窗口
使用
map
对字母出现的位置进行记录,这样在出现相同字母时,就能够从上一次出现的位置向后开始寻找。
需要注意句话it->second+1>=i
, 需要对位置加1后再比较,这样才能保证i
的坐标计算正确。int lengthOfLongestSubstring(string s) { int lengthMax = 0; map<char, size_t> mapPos; for (size_t i=0, j=0; j<s.length(); ++j) { char ch = s[j]; map<char, size_t>::iterator it = mapPos.find(ch); if (it != mapPos.end()) { // 这里说明找到了字符上一次出现的位置 if (it->second + 1>= i) { i = it->second + 1; } } lengthMax = max(lengthMax, int(j - i + 1)); mapPos[ch] = j; } return lengthMax; }
-
-
提高emacs中浏览和选择操作效率的技巧
1 引言
Gaurab Paul的 一篇博文 给了我很大的启发,他详细地介绍了emacs中的相关概念,并提供了许多充满想象力的小技巧。作为emacs的入门级选手确实学到了很多,也拓宽了自己的思路。
我最初的开发环境是Visual Studio,这一类比较大型的IDE集成了许多功能,但同时也会束缚住使用者的想法。通过这篇文章我感受到的由普通操作指令能组合成的新编辑方式。
如果英文水平允许的话,非常推荐阅读一下原版的博文,原文中有更加丰富形象的图片示例,无论是跟我一样刚刚入门emacs的新手,还是经验丰富的老兵,都能够从中获得启发。下面,我结合自己的理解和收获谈谈emacs中操作的体会。
2
point
、mark
和region
的概念我之前进行代码段复制的操作是十分基础的,用
C-@
模拟鼠标按下,方向键模拟鼠标拖动,在鼠标拖动的过程中就形成了一个选区,然后用M-w
对这个选区进行复制操作,用C-y
粘贴被复制的内容。以上操作带出了几个非常重要的概念 。在emacs中,鼠标光标所在位置被称作
point
;组合键C-@
执行的是set-mark-command
命令,就是将point
所在位置标记为mark
;通过移动光标,也就是移动point
后,在point
与mark
之间就形成了region
。3
region
操作技巧3.1 调整
region
的大小下面就来介绍一个非常重要的命令
exchange-point-and-mark
,这个命令默认被绑定在组合键C-x C-x
上,从字面意思上很容易理解这条指令的作用,就是交换mark
和point
的位置。这样做的意义在于能够方便地切换region
的可动边界,这样能够使region
方便地分别从两端调整大小。
下面的示例是截取自Paul的博文,需要注意的是,他习惯于使用C-SPC
调用set-mark-command
。Lorem ipsum dolor sit amet ^ Cursor Point | Mark | Lorem ipsum dolor sit amet ^ C-spc Mark Point | ----region--| | | Lorem ipsum dolor sit amet move ^ forward -> Mark Point | ----region--| | | Lorem ipsum dolor sit amet ^ C-x C-x Point Mark | ----region--| | | Lorem ipsum dolor sit amet Point and mark interchanged
3.2 使用
region
进行重复性输入对于
region
相关的操作,通常是对已经存在的代码段进行编辑的,如果我们在输入之前就知道有许多字段是需要重复输入的,那么就可以在输入之前设置好mark
,对输入后形成的region
完成复制。这个技巧在特定情况能够很大地提升输入效率,但是我个人认为,想要在实战中完成这个操作,还需要保证非常清晰的思路。可以通过下面的示例感受这种操作带来的方便(示例截取自Paul的博文)。下面给出了详细的操作解析
class ^ C-spc => Activate mark class Foo ^ M-w => Foo has now been killed (copied) class Foo extends React.Component< ^ C-spc => Activate mark class Foo extends React.Component< ^ C-y => Yank (paste) Foo class Foo extends React.Component<FooProps ^ M-w => FooProps has now been killed (copied) class Foo extends React.Component<FooProps> // Later interface ^ C-y => Yank FooProps interface FooProps {}
3.3 框选一个矩形的
region
使用
rectangle-mark-mode
命令,默认快捷键C-x-SPC
,能够框选出一个矩形的region
。对于矩形region
,Paul给出的示例是复制dired
中的多个文件名称,貌似其他合适的使用场景不太多。4 其他插件支持
有些插件拓展能够实现光标的快速定位,如 helm-swoop 和 avy 。
4.1 helm swoop
4.2 avy
avy 的思路非常独特,这样的跳转和定位让我想起了Chrome浏览器中的Vimium插件,他允许我们使用更少的按键就能跳转到当前buffer中的任意位置,略微遗憾的是它只支持拉丁字母,不过在编写代码的大多数情况下是够用的。
下面的图片来自Paul的博文
5 参考资料
-
shell的输出重定向
引言
在linux中借助shell等命令行工具能够很方便地与操作系统交互,可以在shell中将命令或程序的输入结果重定向到特定地方,很方便地实现一些功能。这个技巧十分实用,使用输出重定向能够极大地简化我们的日常操作。
使用尖括号完成重定向
示例如下,运行下面的命令能够把
ls
命令的运行结果写入到ls-output.txt
中。使用>
会把程序运行时本该输出到stdout
的内容重定向到指定名称的文件中。ls . > ls-output.txt
可以在
>
左面写上数字和&
,用以标识在重定向时的特殊用法。下面会给出一些特殊用法的实例。重定向
stdout
到指定文件中ls . 1> ls-output.txt
重定向
stderr
到指定文件中ls . 2> ls-error.txt
将
stdout
和stdout
合并再重定向到指定文件中ls . 2>&1 ls-output-and-error.txt
以下命令具有相同的效果
ls . &> ls-output-and-error.txt
将程序的输出丢掉
可以将输出重定向到一个特殊的文件
/dev/null
,所有写入到这个文件的内容都会被丢弃掉。program > /dev/null
将输出追加到指定文件尾部
使用一个尖括号(
>
)能够将输出重定向到文件中,在写入文件时会覆盖掉其中的内容。如果想保留文件中的原始内容,则可以用两个尖括号(>>
),这样就能将输出追加到文件的尾部。示例代码如下:echo test >> file-output.txt
使用管道完成重定向
使用管道符号
|
能够将一个程序的输出重定向到另一个程序的输入中去。下面的命令会将ls
的输出(stdout
)重定向到grep
的输入(stdin
)中去。管道命令在linux中是最常见的用法。ls | grep <pattern>
-
libshmcache源码阅读笔记
引言
由于在工作中需要开发一套内存缓存服务,使用了共享内存作为多进程间的数据共享。为了提高共享内存缓存服务的性能,我找了一个类似的较为成熟的开源项目 libshmcache ,通过研究源码学习其中的优点并改进自己的模块。
libshmcache与redis相似的是都使用内存进行数据缓存;与redis不同的是,redis使用的进程自己申请的动态内存,而libshmcache使用的是共享内存。使用共享内存就意味着libshmcache主要的应用场景是同一台主机上的数据缓存。
我花了一周时间阅读了比较感兴趣的部分代码,收获不少,现就以下几个方面总结一下自己的心得:
- 纯C语言开发的代码风格
- hash table的原理和实现
- gcc原子化操作接口
- 有锁写和无锁读的实现细节
- 共享内存的两套函数接口(POSIX和SystemV)
纯C语言开发时的代码风格
我在工作中使用比较多的开发语言是C++,对于C语言编写的这样规模的项目,还是第一次仔细深入地研究。C语言使用
struct
作为大多数自定义数据结构的关键字,相对于C++能够使用成员函数能够对类进行功能拓展,C语言比较常用的是将这个对象作为输入参数传到函数中。纵观所有项目代码,我感受比较深的就是使用结构体中嵌套匿名结构体,这样做能够增强数据结构的层次感,示例代码如下:
struct shmcache_context { pid_t pid; int lock_fd; //for file lock int detect_deadlock_clocks; struct shmcache_config config; struct shm_memory_info *memory; struct { struct shmcache_segment_info hashtable; struct { int count; struct shmcache_segment_info *items; } values; } segments; struct shmcache_value_allocator_context value_allocator; struct shmcache_list list; //for value recycle bool create_segment; //if check segment size };
注意
shmcache_context
中的匿名结构体segments
和values
,这样的写法体现了相互包含关系,也使后续的操作该数据结构的语句更加容易理解。另外对于联合体和位域这两种技术也是我在之前开发中使用比较少的,通过阅读源码能够让我对其有了更深刻的理解。示例代码如下:
union shm_hentry_offset { int64_t offset; struct { int index :16; int64_t offset :48; } segment; };
这段代码使用了联合体赋予了
shm_hentry_offset
两种访问方式,又使用了位域将int64_t
分割为两段。hash table的原理和实现
libshmcache内部使用的是hash table做内部缓存的数据结构,这使查找的时间复杂度是O(1)。
之前看过一些介绍hash table的资料,对hash table的工作原理是有过一个基础的了解的,这次通过阅读源码,能够了解到hash table在代码实现上更加细节的内容。
对于hash计算中出现的hash值冲突,即在hash计算时出现了两个不同的key在经过hash计算后得到的bucket相同,libshmcache采用的解决方案是使用linked list来存放这些相同bucket对应的value。gcc原子化操作接口
使用原子化操作接口能够解决一些并发读写问题,原子化操作相对于互斥锁执行更快。原子化操作也是一种无锁编程的方式。
有锁写和无锁读的实现
在libshmcache中,写操作通过
pthread_mutex_t
进行同步,而读操作是无锁的。
对于写操作来说,需要对hash table进行操作,这肯定是需要同步的。
将pthread_mutex_t
保存在共享内存中,不同的进程通过映射共享内存就能获得同一个互斥量,通过这个互斥量就能完成进程间同步。共享内存的两套函数接口(POSIX和SystemV)
在linux上使用共享内存时有两套接口
mmap
和shmget
。mmap
是POSIX
标准的接口,而shmget
是System V
标准的接口,两者都能够实现进程间共享内存,但他们在使用上还是有些区别的。对于mmap
来说,需要在硬盘上创建一个文件,再将该文件映射到内存中。对于shmget
来说,需要指定一个key,不同的进程通过相同的key就能映射到同一片内存。 - 纯C语言开发的代码风格
-
C/C++中的位域
什么是位域
维基百科 给出了以下解释
位域(或称“位段”,bit field)为一种数据结构,可以把数据以位的形式紧凑的储存,并允许程序员对此结构的位进行操作。
cppreference 给出了以下定义
Declares a class data member with explicit size, in bits. Adjacent bit field members may be packed to share and straddle the individual bytes.
这种数据结构的好处:
- 可以使数据单元节省储存空间,当程序需要成千上万个数据单元时,这种方法就显得尤为重要。
- 位段可以很方便的访问一个整数值的部分内容从而可以简化程序源代码。
而位域这种数据结构的缺点在于,其内存分配与内存对齐的实现方式依赖于具体的机器和系统,在不同的平台可能有不同的结果,这导致了位域在本质上是不可移植的。
位域的语法
下面就是位域的语法格式。
identifier(optional) attr(optional) : size
注意 :
-
size
的大小不能超过identifier
所包含最大比特位个数。
-
identifier
为空时表示对应的size
个数的比特位不使用
-
size
为0时表示根据前类型强制补齐
以下为代码示例。
struct S1 { // will usually occupy 2 bytes: // 3 bits: value of b1 // 2 bits: unused // 6 bits: value of b2 // 2 bits: value of b3 // 3 bits: unused unsigned char b1 : 3, : 2, b2 : 6, b3 : 2; }; struct S2 { // will usually occupy 2 bytes: // 3 bits: value of b1 // 5 bits: unused // 6 bits: value of b2 // 2 bits: value of b3 unsigned char b1 : 3; unsigned char :0; // start a new byte unsigned char b2 : 6; unsigned char b3 : 2; };
在
size
取0时如何理解对于
size
取0时的各种情况进行了尝试,详细用例如下。思路是使用联合体能比较方便地将内存分布表示出来。
如下所示,定义了如下几种情况。case1
union un1 { int n1; struct { int nn1 : 4; int : 0; int nn2 : 16; } st; };
对联合体中的结构体位段进行赋值,并将联合体中的内容打印出来。
union un1 u1; u1.st.nn1 = 1; u1.st.nn2 = 1; printf("union u1.n1=0x%08x sizeof(un1)=%zu\n", u1.n1, sizeof(un1));
输出结果为
union un1.n1=0x00000001 sizeof(un1)=8
case2
union un1 { int n1; struct { int nn1 : 4; char : 0; int nn2 : 16; } st; };
输出结果为
union un1.n1=0x00000101 sizeof(un1)=4
case3
union un1 { int n1; struct { int nn1 : 4; short : 0; int nn2 : 16; } st; };
输出结果为
union un1.n1=0x00010001 sizeof(un1)=4
case4
union un1 { int n1; struct { int nn1 : 8; char : 0; int nn2 : 16; } st; };
输出结果为
union un1.n1=0x00000101 sizeof(un1)=4
struct
与class
关键字多数例子都是以
struct
作为位域的组织标识,在C++中能否使用class
作为位域的标识符呢?
经过测试,是可以使用class
关键字的,但是需要注意class
的默认访问控制属性为private
。位域的常见应用场景
为什么要使用位域?位域适合那些情况?
位域的主要使用目的是节省对象的内存使用。在存放一些比较小的数据时,使用位域能够使字节中的每个比特位合理地利用起来,避免内存浪费。
比较典型的应用是描述硬件寄存器。如果有32个一组的寄存器,每个寄存器代表一个比特位,就可以使用位域表示这组寄存器。
C++中的位操作接口
C++中也提供了一套位操作的接口
std::bitset
,这套接口提供了指定比特位数据的操作接口。 - 可以使数据单元节省储存空间,当程序需要成千上万个数据单元时,这种方法就显得尤为重要。
-
GCC的内存原子化操作函数接口
1 原子化操作
在并发编程中,一个操作或一组操作是原子操作、可线性化操作、不可分操作或不可中断操作(atomic, linearizable, indivisible, uniterruptible),表示该操作执行时不可被中断的。操作的原子性能够保证操作在执行时免受中断、信号、并发进程线程的影响。另外,原子操作大多只有两种结果,要么成功并改变系统中对应的状态,要么没有相关效果。
-
在gdb中查看指定内存地址的内容
调试 C/C++ 程序时,需要打印指定内存地址的内容。我最近调试程序中的序列化模块时,需要将类对象按照指定的格式转化为二进制流,为了验证转化结果,在调试时就需要将指定内存地址中的内容打印出来。
-
同步和异步、阻塞和非阻塞
1 同步和异步
同步和异步指的是在进行I/O操作完成之前,是否允许其他处理步骤继续执行。
计算机中的I/O操作相对于数据处理操作时十分耗时的。一个简单的I/O操作方式就是启动连接并等待操作完成,但是这样的操作(同步阻塞I/O)在通信过程中会阻塞进程的处理进度。
相应的,可以在启动通信的同时进行其他的处理,并不需要等待I/O操作的完成,这样的操作就被称作是异步I/O。那些依赖于I/O操作执行完成的任务会阻塞等待I/O操作的完成,其他不依赖与I/O操作的任务能够继续执行。同步模型常用的函数接口:
read
,write
,send
,recv
异步模型常用的函数接口:aio_write
,aio_read
1.1 POSIX AIO
在头文件
aio.h
中定义,链接时使用-lrt
函数接口
异步写操作int aio_read(struct aiocb* aiocbp);
异步读操作
int aio_write(struct aiocb* aiocbp);
获取异步操作结果
int aio_return(struct aiocb* aiocbp);
获取异步操作中的错误
int aio_error(struct aiocb* aiocbp);
示例代码: github gist
1.2 Linux AIO
在头文件
libaio.h
中定义,链接时使用-laio
函数接口
需要注意的是aio的函数接口需要借助syscall
进行调用。
创建aio context对象int io_setup(unsigned nr, aio_context_t* ctxp);
销毁aio context对象
int io_destroy(aio_context_t ctx);
提交异步操作
int io_submit(aio_context_t ctx, long nr, struct iocb** iocbpp);
获取异步操作结果
int io_getevents(aio_context_t ctx, long min_nr, long max_nr, io_event* events, struct timespec* timeout);
示例代码:
1.3 POSIX AIO与Linux AIO的区别
On linux, the two AIO implementations are fundamentally different.
The POSIX AIO is a user-level implementation that performs normal blocking I/O in multiple threads, hence giving the illusion that the I/Os are asynchronous. The main reason to do this is that:- it works with any filesystem
- it works (essentially) on any operating system (keep in mind that gnu's libc is portable)
- it works on files with buffering enabled (i.e. no ODIRECT flag set)
The main drawback is that your queue depth (i.e. the number of outstanding operations you can have in practice) is limited by the number of threads you choose to have, which also means that a slow operation on one disk may block an operation going to a different disk. It also affects which I/Os (or how many) is seen by the kernel and the disk scheduler as well.
The kernel AIO (i.e. iosubmit() et.al.) is kernel support for asynchronous I/O operations, where the io requests are actually queued up in the kernel, sorted by whatever disk scheduler you have, presumably some of them are forwarded (in somewhat optimal order one would hope) to the actual disk as asynchronous operations (using TCQ or NCQ). The main restriction with this approach is that not all filesystems work that well or at all with async I/O (and may fall back to blocking semantics), files have to be opened with ODIRECT which comes with a whole lot of other restrictions on the I/O requests. If you fail to open your files with ODIRECT, it may still "work", as in you get the right data back, but it probably isn't done asynchronously, but is falling back to blocking semantics.
Also keep in mind that iosubmit() can actually block on the disk under certain circumstances.在Linux上两种AIO是完全不同的;
POSIX AIO实现在用户层,实际上进行的操作是普通的多线程阻塞操作,表现为I/O操作是异步的,这种AIO的优点是兼容性和可移植性好,缺点是操作队列长度受限于最大线程数量。
Linux AIO是内核提供的AIO函数接口,I/O操作请求的队列在内核中维护,这种AIO的缺点是并不支持所有的文件系统,Linux AIO在某些情况下的磁盘操作是会阻塞的。2 阻塞和非阻塞
阻塞与非阻塞的概念针对的是函数是否会立即返回。
非阻塞模型常与IO复用技术组合使用。
可以通过函数将IO设备设置为非阻塞模式。3 如何理解阻塞非阻塞与同步异步的区别
在处理 IO 的时候,阻塞和非阻塞都是同步 IO。
只有使用了特殊的 API 才是异步 IO。 - it works with any filesystem
-
查看Linux系统的相关信息
查看Linux系统相关信息有助于排查和解决软件和硬件的兼容性问题。系统信息包括硬件信息和软件信息,硬件信息主要有CPU信息、内存信息、PCI信息、USB信息、硬盘信息等等。软件信息主要有系统版本、分区使用状态等等。本文主要介绍了获取当前Linux系统信息的命令。
查看系统相关信息
uname
指令提供了查询系统信息的功能,使用该命令能够快速获取操作系统信息概览。查看内核名称
uname -s
查看处理器类型
uname -p
查看硬件架构
uname -i
查看内核版本
uname -r
查看所有系统信息
uname -a
查看操作系统信息(发行版信息)
cat /etc/os-release cat /proc/version lsb_release -a hostnamectl
查看CPU相关信息
lscpu
指令能够查看当前系统中CPU的详细信息,包括型号、主频、构架、大小端等信息。查看硬盘相关信息
lsblk
指令能够查看块设备(block device)的详细信息,块设备主要指系统中的存储设备如硬盘和闪存。查看PCI设备的相关信息
lspci
指令能够查看PCI设备的信息,PCI设备包括USB、显卡、串口、网卡等其他外围设备。输出树形结果
lspci -t
输出详细信息
lspci -v lspci -vv
查看USB设备的相关信息
lsusb
指令能够查看USBS设备的信息。查看文件系统相关信息
fdisk
命令能够查看和操作linux系统的分区表。查看文件系统信息
fdisk -l
df
命令能够查看分区信息和硬盘使用信息使输出信息更容易理解
df -h