Software

一个例子讲清楚线程间同步、互斥量、条件变量、队列、内存池

前段时间有朋友想要了解一下多线程编程,正好有个项目上有这么个例子可以抽出来讲一讲。只要搞清楚这个例子,就一下子掌握了线程间同步、互斥量、条件变量、队列、内存池的概念和使用。 首先,线程间同步的概念。 比如,学过数字电路的人都知道,两个时钟域的信号如果没有经过同步直接接到一起的话,会引起亚稳态。原因是如果恰好输入信号在时钟边沿附近变化的话(不满足建立保持时间的情况下),信号可能处于一个中间电平,这样会导致触发器处于一个振荡状态,引起整块数字电路的不稳定。这就是数字电路中异步的概念,两个时钟都是各自free running,彼此没有关系。 再比如单片机程序中,各个不同的中断程序或者跟主程序间是异步的,因为主程序在执行的过程中随时可能被进来的中断打断,如果中断和主程序之间要通过一个共享的变量传递数据,你就要注意这个共享的变量的保护。假如主程序只读取了一半的数据而被中断打断,然后中断程序中又更新了整个变量,这样的回到主程序继续执行时读到的数据就有一半是上一次的,一半是更新过的。这样的结果显然不是我们想要的。这里只是举了一个很明显的例子。更多的情况可以搜索一下“原子操作”。 所以在多线程环境下,我们就要注意线程间共享变量的保护,这块敏感区域叫临界区(Critical area)。在单片机中,我们用中断开关来保护共享变量读写操作的完整性。在操作系统中,我们用的是互斥锁(mutex)来占有这个变量,防止它被多个线程同时访问。当一个线程访问当前已经被另一个线程占有的变量时,就会进入阻塞态,直到另一个线程完成解锁操作后,这个线程将得到继续执行。 互斥锁(mutex)是多线程编程时最重要的一个工具,用来解决多线程竞争同个资源的问题。其最底层的实现都是一个原子操作来界定lock or unlock。 接下来的例子创建了两个线程,一个是producer, 另一个是cusumer, 它们两个是异步的,中间通过一个队列来通讯。producer 向队列中发送数据,cusumer读取数据。模拟了一个场景:producer 以较快的速度向队列写数据,cusumer 处理数据较慢。这在图像帧处理时经常会碰到CPU处理和发送数据较慢,而外设采集速度较快的情况,这样多余的帧将被丢弃。队列节点使用自己写的一个内存池来分配,在malloc_node 从内存池(free_queue)里取出node; release_node 时把节点放回资源池。当对free_queue 进行操作的时候都要加锁,因为malloc_node 和release_node 可能被不同的线程调用,必须对free_queue 进行保护。这样的函数称之为是线程安全的。同理对enqueue,dequeue的操作也要对队列进行保护。 然后使用条件变量来通知consumer 队列有新数据到来。条件变量同样是被多个线程调用,也是需要带一个mutex 来进行保护的。当条件不满足时,线程会解锁mutex 进入block状态等待消息,这样才不会一直占有CPU。当条件满足或者超时时,才继续执行下面的程序。 例子中使用了pthread(POSIX thread) 的实现。其实各大操作系统都有自己的实现,FreeRTOS, Linux kernel等等,都可以拿代码过来看看学习。 请看这个多线程的例子,可以在online gdb 中运行调试: #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <pthread.h> #include <stdbool.h> #include <assert.h> #include <sys/time.h> #include <errno.h> /\*\*\*\*\*\*\*\*\*\*\*\* queue manage \*\*\*\*\*\*\*\*\*\*\*/ typedef struct Node { void \*data; struct Node \*next; }queue\_node\_t; #define BUFFER\_POOL\_SIZE (640 \* 480) #define BUFFER\_POOL\_NUM 5 struct pbuf{ uint32\_t len; uint8\_t payload\[BUFFER\_POOL\_SIZE\]; }; typedef struct QueueList { int sizeOfQueue; uint16\_t memSize; queue\_node\_t \*head; queue\_node\_t \*tail; }queue\_t; /\* to inform consumer\_thread \*/ static pthread\_cond\_t cap\_cond; static pthread\_mutex\_t cap\_mutex; /\* stream queue for communicate between two threads \*/ static queue\_t strm\_queue; static pthread\_mutex\_t strmq\_mutex; int strm\_queue\_init(){ queue\_t \*q = &strm\_queue; q->sizeOfQueue = 0; q->memSize = 0; q->head = q->tail = NULL; if (pthread\_cond\_init(&cap\_cond, NULL) !

git 中心服务搭建

中心服务方式选择 git 本身是一个分布式的版本管理系统,但如果要设置一个中心库方便很多开发者同步,或者像SVN 一样使用它,就需要搭建一个中心库。有几种方式可以选择: gitosis : 这个是比较老的方式。不推荐 详情参考: https://git-scm.com/book/en/v1/Git-on-the-Server-Gitosis GitLab: git 结合web 服务来管理,方便issue 和权限管理。比较推荐。收费版还可提供更多功能。参考:https://about.gitlab.com/install/ 只用ssh git 用户管理 开一个git 用户,设定好权限,也比较方便。但是缺少管理issue 功能。 参考:https://git-scm.com/book/en/v2/Git-on-the-Server-Setting-Up-the-Server ssh git用户设置  ssh key证书生成 $ ssh-keygen –t rsa –C “user@host”  将id_rsa.pub 发给git服务器管理员添加进/home/git/.ssh/authorized_keys 即可,或者直接用 ssh-copy-id 到服务器即可。 $ git clone git@IP:/srv/git/test.git 有几个注意点: 使用 ssh key 登陆 git 用户时,home目录只能是git 可写的,否则当git组包含多个用户时会出现不用能ssh key 登录的问题。具体debug ssh key 登录问题,可以查看 /var/log/auth 下的日志来解决。.ssh/ 的权限是700,.ssh/authorized_keys 权限是600。 最后用chsh 修改 git用户shell 为 git-shell,不让git 用户有其它多余的权限。 创建仓库可以用git 组的其它用户来创建。注意使用newgrp 将创建文件夹时用户的默认组改成git, 这样整个git 组的用户都有读写权限。 一些实用的git 命令记录 git checkout --patch BRANCH FILE git checkout --theirs PATH/FILE git clean -n //演习 git ls-files git ls-files | xargs -n 1 dirname | uniq git diff master sync --name-only git config --global core.

c/c++编译相关技巧总结帖

链接时忽略文件中未用到的函数或者对象。这在移植代码过程中很有用, 我们就不需要去删除或者注释掉那些大量没用到的对象或者不需要去链接的对象。 For GCC, this is accomplished in two stages: First compile the data but tell the compiler to separate the code into separate sections within the translation unit. This will be done for functions, classes, and external variables by using the following two compiler flags:-fdata-sections -ffunction-sections Link the translation units together using the linker optimization flag (this causes the linker to discard unreferenced sections):-Wl,--gc-sections So if you had one file called test.

总结几个C 语言比较高级的编写技巧

看了一些源代码,这里总结几个不常见的,但是比较好用的C 用法。也许这些功能在C++中有扩展,但是个人基本不用C++。其实这些应该是属于预处理器的功能。若是之前没有见过,遇到时会感觉云里雾里的。 结构体直接赋值 C99 支持直接将一个结构常量赋值给对应的结构体变量。而不用每个结构成员逐一赋值。 typedef struct rectangle{ int x0; int y0; int x1; int y1; }rect_t; void main() { rect_t a = {0, 0, 0, 0}; a = (rect_t) {0, 0, 1, 2}; } 如何在编译阶段就知道结构体长度? 可以定义一个数组指针: char (*__kaboom)[sizeof( YourTypeHere )] = 1; 编译时它就会产生一个warning,这样就可以知道它的长度了: warning C4047: 'initializing': 'DWORD (*)[88]' differs in levels of indirection from 'int' 同样也可以用 offsetof() 就知道成员的偏移量了。 直接定义字符串: 我们可以用#define 定义字符串常量,比如: #define HELLO “hello” 这样就相当于是: char *HELLO= “hello”; 而且使用#define 来定义的好处是可以直接拼接字符串,这些应该都是由预处理器来帮我们处理的(没有仔细考证,但想像其中原理应该是这样)。比如:``` #define HELLO “hello” #define WORLD “world”

用ECS在云端搭建SVN服务

应用环境: ECS 云端系统:Ubuntu 14.04-x64 本地系统:window 使用工具:cygwin + ssh 原版文档: http://www.shayanderson.com/linux/install-and-setup-subversion-server-on-ubuntu-1210-server-with-multiple-repositories.htm 一、安装及目录配置 1. 登录云端服务器 ssh xxx@[IP] 2. 安装subversion apt-get install subversion 检查是否安装完成 svn --version 3. 先建立一个Repositories 目录放置工程 mkdir /home/repos 在目录底下再建立工程 svnadmin create /home/repos/project1 svnadmin create /home/repos/project2 注:删除则用 svnadmin deltify 4. 修改目录组权限让一用户组都可管理此目录 chmod -R g+rws /home/repos //设置组权限 sudo groupadd svn //添加组 chgrp -R svn /home/repos //修改group ownership usermod -a -G svn [username] //添加用户进组 groups // 检查用户是否被添加进组 二、云端上测试SVN服务 1. 新建一个文件夹 mkdir /home/tmp svn checkout file:///home/repos/project1 /home/tmp // checkout到目录tmp 2.