线程栈如何分配

测试环境:
Linux centos-7.shared 3.10.0-693.5.2.el7.x86_64 #1 SMP Fri Oct 20 20:32:50 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

一个进程的虚拟地址空间一般可以大致划分为代码区(text)、只读数据区(rodata)、初始化数据区(data)、为初始化数据区(bss)、堆(heap)、共享内存区(.so,mmap的地方)、栈(stack)、内核区(kernel)。

在这里插入图片描述

对于 Linux 进程或者说主线程,其 stack 是在 fork 的时候生成的,实际上就是复制了父亲的 stack 空间地址,然后写时拷贝 (cow) 以及动态增长。
然而对于主线程生成的子线程而言,其 stack 将不再是这样的了,而是事先固定下来的。
线程栈不能动态增长,一旦用尽就没了,这是和生成进程的 fork 不同的地方。

==线程(非主线程)的栈的大小是固定的==,其会在空闲的堆(堆顶附近自顶向下分配)或者是空闲栈(栈底附近自底向上分配),因此线程栈局部函数中分配的变量是存放到各自分配的栈空间,因此可以说是线程私有的,又因为该线程栈的边界是设定好的,因此该线程栈的大小的固定的。

测试

ulimit -a 查看操作系统的相关限制:
可以看到 stack size 的限制是 8192kb 也就是 8MB。
注意这里的 8MB 是指每个被创建的 thread 的 stack 都是这么大。

1
2
3
4
[parallels@centos-7 LinuxCode]$ ulimit -a
......
stack size (kbytes, -s) 8192
......

测试代码:
创建了 3 个 thread,执行 ThreadEntry。
编译后跑起来!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

void*
ThreadEntry(void* args)
{
(void) args;
while (1)
{
sleep(1);
}
}

int main()
{
pthread_t tid1, tid2, tid3;
pthread_create(&tid1, NULL, ThreadEntry, NULL);
pthread_create(&tid2, NULL, ThreadEntry, NULL);
pthread_create(&tid3, NULL, ThreadEntry, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
return 0;
}

==从 heap 的顶部向下分配。==
ps aux | grep a.out 查看 pid
cat /proc/[pid]/maps 这个显示进程映射了的内存区域和访问权限。
可以看到:在 heap 下面连续的几个属性为 rw-p 的地址大小刚好都为 8192kb。并且每个都在边界穿插了一个大小为 1000H(4096kb) 的边界空间。

在这里插入图片描述

==从 stack 底部向上分配==
ulimit -s unlimited 设置 stack size 为 unlimited,注意虽然设置了stack size为无限,但是实际上其并不是无限的,而也是固定大小的线程栈,大小为1mb。
然后 cat /proc/[pid]/maps 查看虚拟地址空间的映射。
可以看到,这种情况下线程栈是分配在 stack 底附近,自底向上生长的。
在这里插入图片描述
在这里插入图片描述

结论

==不管线程栈是在堆分配还是在栈分配,其都是固定大小的,有边界的。==


参考:
https://blog.csdn.net/qq_16097611/article/details/82592873
https://blog.csdn.net/yangkuanqaz85988/article/details/52403726
https://blog.csdn.net/lijzheng/article/details/23618365

EOF

阅读全文
线程基础知识

本文测试代码的运行环境:
Centos7 x86_64
Kernel 3.10.0-693.5.2.el7.x86_64
gcc 版本 4.8.5
CPU:2 核

线程概念

线程是运行在进程之中的一个处理任务的分支,一个进程都包括一个主线程。
进程:资源分配、管理的基本单位(管理内存、管理打开的文件等)。
线程:调度、执行的基本单位。
在 Linux 中线程也叫做轻量级进程 ==LWP==。
每次创建一个新的进程,会分配一个新的虚拟地址空间。
每次创建一个新的线程,线程共用原来的虚拟地址空间。

从 Linux 内核的角度来说,其实它并没有线程的概念。Linux 把所有线程都当做进程来实现,它将线程和进程不加区分的统一到了 task_struct 中。

线程之间共用的资源

  1. 虚拟地址空间。
  2. 文件描述符表。

线程创建的时候,加上了 CLONE_VM 标记,这样==线程的内存描述符将直接指向父进程的内存描述符。==

1
2
3
4
5
if (clone_flags & CLONE_VM) {
// current 是父进程而 tsk 在 fork() 执行期间是共享子进程
atomic_inc(&current->mm->mm_users);
tsk->mm = current->mm;
}

线程之间不共用的资源

  1. 栈。
  2. 上下文信息(寄存器信息)。
  3. errno(每个线程有自己单独的错误码)。

对于 Linux 进程或者说主线程,其 stack 是在 fork 的时候生成的,实际上就是复制了父亲的 stack 空间地址,然后写时拷贝 (cow) 以及动态增长。
然而对于主线程生成的子线程而言,其 stack 将不再是这样的了,而是事先固定下来的。
线程栈不能动态增长,一旦用尽就没了,这是和生成进程的 fork 不同的地方。

线程的优点

相比于进程来说:

  1. 创建和销毁开销更小。
  2. 切换调度的开销更小。
  3. 线程占用的资源更小。

多线程程序能够充分利用多核处理器。
栗子:==因为我的虚拟机是 2 核的,所以 CPU 最多使用是 200%==
在这里插入图片描述

线程的缺点

  1. 程序的健壮性降低,一个线程的异常终止会导致整个进程异常终止。
  2. 编程 && 调试难度增加(引入了线程安全问题)。

线程的用途

  1. 提升 CPU 密集型程序执行效率。
  2. 提高 IO 密集型程序的体验。
    • 通过网络进行 IO。
    • 响应 UI 界面。

EOF

阅读全文
Redis基础知识

Redis 5.0.7。

Redis 常用命令、5 种数据类型。

常用命令

DEL key【删除一个 key,所有类型都可以删除】

DUMP key【序列化给定 key,返回被序列化的值】

EXISTS key【判断 key 是否存在】

EXPIRE key second【设置 key 的过期时间】

TTL key 【查看 key 的剩余时间,默认 -1 表示永久,-2 表示已经过期】

PERSIST key 【移除 key 的过期时间】

KEY pattern 【查询给定模式的 key】

RANDOMKEY 【随机返回一个 key】

RANAME key newkey 【修改 key 的名称】

MOVE key db 【将 key 移动到其他数据库中】

TYPE key 【返回 key 所储存的值的类型】

注意:

  1. KEY pattern 的 pattern 为 *代表所有,?代表单个字符。

  2. key 可以是中文,例如:set 你好 hello,但是 redis-cli 显示不出来中文,查询时候得用中文查询。

    1
    2
    3
    4
    5
    6
    7
    127.0.0.1:6379[1]> set 你好 hello
    OK
    127.0.0.1:6379[1]> keys *
    1) "\xe4\xbd\xa0\xe5\xa5\xbd"
    2) "a"
    127.0.0.1:6379[1]> get 你好
    "hello"
  3. key 不要太长,尽量不要超过 1024 字节。不仅消耗内存,也会降低查找的效率。

  4. key 不要太短,太短可读性会降低。

  5. 一个项目中,key 最好使用统一的命名模式,如 user:123:password

  6. key 区分大小写。

EXPIRE key second 的使用场景:

  1. 限时的优惠活动
  2. 网站数据缓存
  3. 手机验证码
  4. 限制网站访客频率

数据类型

string

string 类型是二进制安全的,redis 的 string 可以包含任何数据,如图像、序列化对象。一个键最多能存储512MB。二进制安全是指,在传输数据的时候,能保证二进制数据的信息安全,也就是不会被篡改、破译;如果被攻击,能够及时检测出来。

  • SET key val

    SETNX key val【当 key 不存在时设置 key 的值(SET if Not eXists)】

  • 删除就用 del 就可以咯

  • INCR key【key 对应的 val 自增 1,如果 key 不存在,val 初始化为 0 再自增1】

    INCRBY key num【num 是增量】

    DECR key 【自减,同上 INCR】

    DECRBY key num 【num 是负增量】

    APPEND key val【字符串拼接,如果不存在则创建并赋值 val】

    GETSET key value【设定 key 的值,并返回 key 的旧值。当 key 不存在,返回 nil】

  • GET key【获取 key 的 val】

    GETRANGE key start end【获取 key 中字符串的子字符串,从 start 开始,end 结束】

    MGET key1 [key2 …] 【获取多个 key】

    GETSET key value【设定 key 的值,并返回 key 的旧值。当 key 不存在,返回 nil】

    STRLEN key 【返回 key 对应 val 的字符串长度】

string 类型的使用场景:

  1. String 通常用于保存单个字符串或 JSON 字符串数据。
  2. 因为 String 是二进制安全的,所以可以把保密要求高的图片文件内容作为字符串来存储。
  3. 计数器,常规 Key-Value 缓存应用,如微博数、粉丝数。INCR 本身就具有原子性特性,所以不会有线程安全问题。

hash

Redis hash 是一个 string 类型的 field 和 value 的映射表,hash特别适用于存储对象。每个 hash 可以存储2^32-1个键值对。可以看成 key 和 value 的 map 容器。相比于 JSON,hash 占用很少的内存空间。

  • HSET key field value【为指定的 key 设定 field 和 value】

    HMSET key field value [field1 value1 …] 【为 key 增加多个 field value 映射】

  • HDEL key field [field1 …]【删除一个或多个指定字段】

  • 修改使用 HSET、HMSET 即可

  • HGET key field【获取指定字段的 value】

    HMGET key field [field1 …] 【获取多个字段的 value】

    HGETALL key【返回 key 对应的所有的值】

    HKEYS key 【返回 key 对应的所有 field】

    HLEN key 【获取 field 的个数】

注意:

  1. s 是一个 string 类型的数据 key,HSET 是不可以覆盖其数据的。

  2. 但是 h 是一个 hash 类型的数据 key,SET 是可以用字符串覆盖其中的数据的。

  3. 相比于存储对象的 string 类型的 json 串,json 串修改单个属性需要将整个值取出来。而 hash 不需要。

  4. 相比于多个 key-value 存储对象,hash 节省了很多内存空间。

  5. 如果 hash 的属性值被删除完,那么 hash 的 key 也会被 redis 删除。

list

类似于 Java 中的 Linklist 类型。

  • LPUSH key val1 [val2 …]【头插法将 val 插入】

    RPUSH key val1 [val2 …]【尾插发将 val 插入】

    LPUSHX key val1 [val2 …]【从左侧插入值,如果 list 不存在,则不操作】

    RPUSHX key val1 [val2 …]【从右侧插入值,如果 list 不存在,则不操作】

  • LPOP key【从左侧 pop 一个元素】

    RPOP key【从右侧 pop】

    BLPOP key [key1 …] timeout【移除并获取列表第一个元素,如果列表没有元素会阻塞列表到等待超时或发现可弹出元素为止,timeout 单位秒】

    BRPOP key [key1 …] timeout【类似上面】

    LTRIM key start stop【对列表进行修改,让列表只保留指定区间的元素,不在指定区间的元素就会被删除】

  • LSET key index value【指定索引的值修改】

    LINSERT key BEFORE|AFTER pivot value【在列表元素前或则后插入元素】

  • LLEN key【获取 key 对应的 list 长度】

    LINDEX key index【获取指定 index 的元素】

    LRANGE key start stop【获取列表指定范围的元素,stop 为 -1 表示最后一个,-2 表示倒数第二个…】

  • 其他

    RPOPLPUSH list1 list2【移除 list1 最后一个元素,并将该元素添加到 list2 左边并返回此元素】

    用此命令可以实现订单下单流程、用户系统登录注册短信等。

应用场景:

  1. 对数据大的集合数据删减

    列表显示、关注列表、粉丝列表、留言评价…分页、热点新闻等。

  2. 任务队列

    list 通常用来实现一个消息队列,而且可以确保先后顺序,不必像 MySQL 那样通过 order by 来排序。

set

唯一、无序。

  • SADD key value1 [value2 …] 【向集合添加成员】

  • SREM key member1 [member2 …]【移除集合中一个或多个成员】

    SPOP key【移除并返回集合中的一个随机元素】

  • SMOVE source destination member【将member元素从source集合移动到destination集合】

  • SCARD key【返回集合成员数】

    SMEMBERS key【返回集合中所有成员】

    SISMEMBER key member【判断 memeber 元素是否是集合 key 成员的成员】

    SRANDMEMBER key [count]【返回集合中一个或多个随机数】

  • 其他

    SDIFF key1 key2【差集;集合1 - 集合2(集合1有的集合2没有的)】

    SDIFFSTORE destination key1 key2【返回集合差集并存储在 destination 中】

    SINTER key1 key2【交集】

    SINTERSTORE destination key1 key2【交集放在 dest 中】

    SUNION key1 key2【并集】

    SUNIONSTORE destination key1 key2【同理】

对两个集合间的数据[计算]进行交集、并集、差集运算。

使用姿势:

  1. 以非常方便的实现如共同关注、共同喜好、二度好友等功能。

    对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存储到一个新的集合中。

  2. 利用唯一性,可以统计访问网站的所有独立 IP。

zset

有序且不重复。每个元素都会关联一个 double 类型的分数,Redis 通过分数进行从小到大的排序。分数可以重复。

  • ZADD key score1 member1 [score2 member2 …]

  • ZREM key member1 [member2 …]【移除指定元素】

    ZREMRANGE key start stop【移除有序集合中给定的排名区间的所有成员(第一名是0)(低到高排序)】

    ZREMRANGEBYSCORE key min max【移除有序集合中给定的分数区间的所有成员】

  • ZCARD key【获取 key 中元素的数量】

    ZCOUNT key min max【计算在有序集合中指定区间(min-max)分数的成员数】

    ZRANK key member【返回元素的索引】

    ZRANGE key start stop【返回索引 start-stop 的元素,stop=-1 表示最后一个元素,从小到大】

    ZREVRANGE key start stop【从大到小】

使用姿势:

  1. 常用于排行榜(成绩、积分榜)。
  2. 还可以用 zset 来做带权重的队列,让重要的任务先执行。

其他功能特性

发布、订阅

Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

Redis 客户端可以订阅任意数量的频道。

比如:你和你的好兄弟都订阅了关注了共青团中央的频道,当共青团中央发送一条动态的时候你和你的好兄弟都会受到这条动态消息。

命令:

SUBSCRIBE channel1 [channel2 …]【订阅一个或多个频道】

PSUBSCRIBE pattern1 [pattern2 …]【订阅符合 pattern 的频道】

PUBLISH channel message【向指定频道发送message】

UNSUBSCRIBE [channel [channel …]]【退订指定频道】

PUNSUBSCRIBE [pattern [pattern …]]【退订符合 pattern 的频道】

使用姿势:

  1. 构建实时的消息系统,比如普通聊天、群聊等功能。
  2. 博客网站订阅,当作者发布就可以推送给粉丝。
  3. 微信公众号模式。

多数据库

SELECT db 切换到 db(0 ~ 15)

MOVE key db 将 key 移动到某个 db

FLUSHDB 清空当前数据库

FLUSHALL 清空所有数据库

事物

事务可以一次执行多个命令,按顺序地串行化执行,执行过程中不允许其他命令插入执行序列中。

  1. Redis 会将一个事务中的所有命令序列化,然后按顺序执行。
  2. 执行中不会被其他命令插入,不允许加塞行为。

MULTI 标记一个事物的开始。

DISCARD 取消事物,放弃执行事物块内的所有命令。

EXEC 执行事物。

WATCH key [key …] 监视一个/多个 key,如果事物执行之前 key 被其他命令所改动,那么事物将被打断。

UNWATCH 取消 WATCH 命令对所有 key 的监视。

栗子1:正常用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> get account:a
QUEUED
127.0.0.1:6379> get account:b
QUEUED
127.0.0.1:6379> DECRby account:a 50
QUEUED
127.0.0.1:6379> INCRBY account:b 50
QUEUED
127.0.0.1:6379> exec
1) "80"
2) "10"
3) (integer) 30
4) (integer) 60

栗子2:类似于运行时错误

1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6379> MULTI 
OK
127.0.0.1:6379> set hello hello
QUEUED
127.0.0.1:6379> incr hello
QUEUED
127.0.0.1:6379> get hello
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) "hello"

栗子3:类似于编译错误

1
2
3
4
5
6
7
8
9
10
127.0.0.1:6379> MULTI 
OK
127.0.0.1:6379> set aaa 123
QUEUED
127.0.0.1:6379> fsadadasd
(error) ERR unknown command `fsadadasd`, with args beginning with:
127.0.0.1:6379> get aaa
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.

栗子4:WATCH 被打断的用法

1
2
3
4
5
6
7
8
9
10
127.0.0.1:6379> WATCH account:b
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> get account:b
QUEUED
127.0.0.1:6379> incrby account:b 10
QUEUED
127.0.0.1:6379> exec
(nil) # 在另一个终端改变了 account:b 的值,所以执行事物的结果是 nil

持久化

  1. RDB

    RDB 是 Redis 默认持久化机制。RDB 相当于快照,保存的是一种状态。

    保存速度、还原速度极快、适用于灾难备份。

    小内存的机器不符合使用。RDB机制符合要求(配置文件中的规则)就会快照。

  2. AOF

    如果 Redis 意外 down 掉,RDB 方式会丢失最后一次快照后的所有修改。如果要求应用不能丢失任何修改,可以采用 AOF 持久化方式。

    AOF:Append-Only File:Redis会将没一个收到写命令都追加到文件中(默认是appendonly.aof)。当Redis重启时会通过重新执行文件中的写命令重建整个数据库的内容。

    产生问题:有些命令是多余的,比如执行了 100 次incr num,99 次都是多余的。


参考:

https://blog.csdn.net/qq_33423418/article/details/101351944


未完,待续。

EOF

阅读全文
Docker实现基础技术

Docker 是一个使用 Linux Namespace 和 Cgroups 的虚拟化工具。

Linux Namespace 和 Cgroups 是什么?有什么用?在 Docker 中是怎么被使用的?

阅读全文
从B站偷小火箭

非专业前端,自己写不出类似的动画,也懒得看,就直接取现成的吧~ 效果如下:

QQ20200111-220049-HD.gif

阅读全文