当前位置: 首页 > 编程日记 > 正文

Linux下多线程编程中信号量介绍及简单使用

在Linux中有两种方法用于处理线程同步:信号量和互斥量。

线程的信号量是一种特殊的变量,它可以被增加或减少,但对其的关键访问被保证是原子操作。如果一个程序中有多个线程试图改变一个信号量的值,系统将保证所有的操作都将依次进行。信号量一般常用于保护一段代码,使其每次只被一个执行线程运行。信号量是用来调协线程对共享资源的访问的。

通过使用信号量可以很好的完成线程同步。两个线程同时监视同一个信号量。A线程增加信号量的值,B线程减少信号量的值。当A线程增加信号量大于0时,B线程的等待信号量就会触发,每触发一次将信号量减1,直到将信号量减为0,B线程继续等待A线程增加信号量。

信号量和互斥锁(mutex)的区别:互斥锁只允许一个线程进入临界区,而信号量允许多个线程同时进入临界区。

信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作。而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这个资源。比如对全局变量的访问,有时要加锁,操作完了,在解锁。有的时候锁和信号量会同时使用的。

信号量:只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减1。若value值不大于0,则sem_wait使得线程阻塞,直到sem_post释放后value值加1,但是sem_wait返回之前还是会将此value值减1.

如果信号量的值大于0表示可用的资源数,小于0表示阻塞的线程数。

互斥锁: 只要被锁住,其他任何线程都不可以访问被保护的资源,也就是说,信号量不一定是锁定某一个资源,而是流程上的概念,比如:有A,B两个线程,B线程要等A线程完成某一任务以后再进行自己下面的步骤,这个任务并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类。而线程互斥量则是“锁住某一资源”的概念,在锁定期间内,其他线程无法对被保护的数据进行操作。在有些情况下两者可以互换。

信号量是一个特殊类型的变量,它可以被增加或减少,但对它的访问都会被保证是原子操作,即使在一个多线程程序中也是如此。也就是说,如果一个程序中有两个或多个线程试图改变一个信号量的值,系统将保证所有的操作都将依次进行。如果换成普通的变量,来自同一个程序中的不同线程的冲突操作将会导致不确定的操作。

两种信号量:二进制信号量和计数信号量。二进制信号量只有0和1两种取值,而计数信号量则有更大的取值范围。如果某个共享资源只能被一个线程访问,那么二进制信号量则是最好的打算;如果有多个线程需要访问共享资源呢,使用计数信号量则是个好的主意。

互斥锁只有0,1两中状态,适合于线程对共享资源的独占访问,很多时候每个资源可以同时被有限的线程访问,此时互斥锁将无法满足;条件变量同步也同样存在这种问题。信号量实际是一种非负整型计数器,可以很好的控制线程之间资源访问,互斥锁能实现的功能,信号量同样可以。

信号量控制资源共享主要是PV原语操作, PV原语是对整数计数器信号量sem的操作。一次P操作使sem减一,而一次V操作使sem加一。进程(或线程)根据信号量的值来判断是否对公共资源具有访问权限。当信号量sem的值大于等于零时,该进程(或线程)具有公共资源的访问权限;相反,当信号量sem的值小于零时,该进程(或线程)就将阻塞直到信号量sem的值大于等于0为止。

信号量的函数都以sem_开头,线程中使用的基本信号量函数有4个,它们都声明在头文件semaphore.h中。

sem_init(sem_t*sem, int pshared, unsigned int value):该函数用于创建信号量。初始化一个定位在sem的匿名信号量。value参数指定信号量的初始值。pshared参数指明信号量是由进程内线程共享,还是由进程之间共享。如果pshared的值为0,那么信号量将被进程内的线程共享,并且应该放置在所有线程都可见的地址上(如全局变量,或者堆上动态分配的变量)。

sem_wait(sem_t*sem):该函数用于以原子操作的方式将信号量的值减1。(原子操作就是,如果两个线程企图同时给一个信号量加1或减1,它们之间不会互相干扰。)但它永远会先等待该信号量为一个非零值才开始做减法。也就是说,如果你对一个值为2的信号量调用sem_wait(),线程将会继续执行,这信号量的值将减到1。如果对一个值为0的信号量调用sem_wait(),这个函数就会等待直到有其它线程增加了这个值使它不再是0为止。如果有两个线程都在sem_wait()中等待同一个信号量变成非零值,那么当它被第三个线程增加一个“1”时,等待线程中只有一个能够对信号量做减法并继续执行,另一个还将处于等待状态。被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一,表明公共资源经使用后减少。

sem_post(sem_t*sem):该函数用于以原子操作的方式将信号量的值加1。用来增加信号量的值当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不再阻塞,选择机制同样是由线程的调度策略决定的。它信号量的值加1同时发出信号来唤醒等待的线程。

sem_destroy:该函数用于对用完的信号量的清理。用来释放信号量sem。

下面是从其他文章中copy的测试代码,详细内容介绍可以参考对应的reference:

test_thread_sem.cpp

// reference: https://software.intel.com/zh-cn/blogs/2011/12/02/linux-3
#include <iostream>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>namespace {int g_Flag = 0;
sem_t sem_mutex; // 用于互斥
sem_t sem_syn; // 用于同步void *thread1(void *arg)
{fprintf(stdout, "Enter thread1\n");fprintf(stdout, "thread1 id: %u, g_Flag: %d\n", (unsigned int)pthread_self(), g_Flag);if (sem_wait(&sem_mutex) != 0) {fprintf(stderr, "pthread1 sem_mutex fail\n");return nullptr;}if (g_Flag == 2)sem_post(&sem_syn);g_Flag = 1;if (sem_post(&sem_mutex) != 0) {fprintf(stderr, "pthread1 sem_post fail\n");return nullptr;}fprintf(stdout, "thread1 id: %u, g_Flag: %d\n", (unsigned int)pthread_self(), g_Flag);fprintf(stdout, "Leave thread1\n");pthread_t tid = pthread_self();fprintf(stdout, "thread1 tid = %u\n", tid);pthread_join(tid, nullptr);fprintf(stdout, "\n");return nullptr;
}void *thread2(void *arg)
{fprintf(stdout, "Enter thread2\n");fprintf(stdout, "thread2 id: %u , g_Flag: %d\n", (unsigned int)pthread_self(), g_Flag);if (sem_wait(&sem_mutex) != 0) {fprintf(stderr, "thread2 sem_wait fail\n");return nullptr;}if (g_Flag == 1)sem_post(&sem_syn);g_Flag = 2;if (sem_post(&sem_mutex) != 0) {fprintf(stderr, "thread2 sem_post fail\n");return nullptr;}fprintf(stdout, "thread2 id: %u , g_Flag: %d\n", (unsigned int)pthread_self(), g_Flag);fprintf(stdout, "Leave thread2\n");pthread_t tid = pthread_self();fprintf(stdout, "thread2 tid = %u\n", tid);pthread_join(tid, nullptr);fprintf(stdout, "\n");return nullptr;
}} // namespaceint main()
{pthread_t tid1, tid2;sem_init(&sem_mutex, 0, 1);sem_init(&sem_syn, 0, 0);fprintf(stdout, "Inter main!\n");int ret = pthread_create(&tid2, nullptr, thread2, nullptr);if (ret != 0) {fprintf(stderr, "%s, %d\n", __func__, strerror(ret));return -1;}ret = pthread_create(&tid1, nullptr, thread1, nullptr);if (ret != 0) {fprintf(stderr, "%s, %d\n", __func__, strerror(ret));return -1;}fprintf(stdout, "Leave main!\n\n");sem_wait(&sem_syn); // 同步等待,阻塞return 0;
}

test_thread_sem1.cpp

// reference: http://man7.org/linux/man-pages/man3/sem_wait.3.html#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <time.h>
#include <assert.h>
#include <errno.h>
#include <signal.h>namespace {sem_t sem;#define handle_error(msg) \do { perror(msg); exit(EXIT_FAILURE); } while (0)void handler(int sig)
{write(STDOUT_FILENO, "sem_post() from handler\n", 24);if (sem_post(&sem) == -1) {write(STDERR_FILENO, "sem_post() failed\n", 18);_exit(EXIT_FAILURE);}
}} // namespaceint main(int argc, char *argv[])
{struct sigaction sa;struct timespec ts;int s;if (argc != 3) {fprintf(stderr, "Usage: %s <alarm-secs> <wait-secs>\n", argv[0]);exit(EXIT_FAILURE);}if (sem_init(&sem, 0, 0) == -1)handle_error("sem_init");/* Establish SIGALRM handler; set alarm timer using argv[1] */sa.sa_handler = handler;sigemptyset(&sa.sa_mask);sa.sa_flags = 0;if (sigaction(SIGALRM, &sa, nullptr) == -1)handle_error("sigaction");alarm(atoi(argv[1]));/* Calculate relative interval as current time plusnumber of seconds given argv[2] */if (clock_gettime(CLOCK_REALTIME, &ts) == -1)handle_error("clock_gettime");ts.tv_sec += atoi(argv[2]);printf("main() about to call sem_timedwait()\n");while ((s = sem_timedwait(&sem, &ts)) == -1 && errno == EINTR)continue;       /* Restart if interrupted by handler *//* Check what happened */if (s == -1) {if (errno == ETIMEDOUT)printf("sem_timedwait() timed out\n");elseperror("sem_timedwait");} elseprintf("sem_timedwait() succeeded\n");exit((s == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
}// ./test_thread_sem1 2 3
// ./test_thread_sem1 2 1

test_thread_sem2.cpp

// reference: https://mahaveerdarade.wordpress.com/2013/09/16/semaphores-in-linux-sem_wait-sem_post-code-examples-in-c/
#include <stdlib.h>
#include <pthread.h> 
#include <stdio.h>
#include <semaphore.h>
#include <unistd.h>namespace {int cnt = 0;
int a[] = {1,2,3,4,5,6,7,8,9};
char arr[] = {'a','b','c','d','e','f','g','h','j'};
sem_t s1;void* pc(void* arg)
{int i = 0;while (i < 9) {fprintf(stdout, "---- Line: %d\n", __LINE__);sem_wait(&s1);while (cnt == 0) {//fprintf(stdout, "---- Line: %d\n", __LINE__);	// 注意:打开此条语句对结果的影响sem_post(&s1);}fprintf(stdout, "%c\n", arr[i++]);sleep(1);cnt=0;sem_post(&s1);}return nullptr;
}void* pi(void* arg)
{int i = 0;while (i < 9) {sleep(2);		fprintf(stdout, "++++ Line: %d\n", __LINE__);		sem_wait(&s1);while (cnt == 1) {sem_post(&s1);}fprintf(stdout, "%d\n", a[i++]);sleep(1);cnt = 1;sem_post(&s1);}return nullptr;
}} // namespaceint main()
{pthread_t t1,t2;sem_init(&s1, 0, 1);pthread_create(&t1, nullptr, pc, nullptr);pthread_create(&t2, nullptr, pi, nullptr);pthread_join(t1, nullptr);pthread_join(t2, nullptr);sem_destroy(&s1);return 0;
}

test_thread_sem3.cpp

// reference: http://www.amparo.net/ce155/sem-ex.html
/* Includes */
#include <unistd.h>     /* Symbolic Constants */
#include <sys/types.h>  /* Primitive System Data Types */ 
#include <errno.h>      /* Errors */
#include <stdio.h>      /* Input/Output */
#include <stdlib.h>     /* General Utilities */
#include <pthread.h>    /* POSIX Threads */
#include <string.h>     /* String handling */
#include <semaphore.h>  /* Semaphore */namespace {/* global vars */
/* semaphores are declared global so they can be accessed in main() and in thread routine,here, the semaphore is used as a mutex */
sem_t mutex;
int counter = 0; /* shared variable *//* prototype for thread routine */
void* handler(void* ptr)
{int x; x = *((int *)ptr);printf("Thread %d: Waiting to enter critical region...\n", x);sem_wait(&mutex);       /* down semaphore *//* START CRITICAL REGION */printf("Thread %d: Now in critical region...\n", x);printf("Thread %d: Counter Value: %d\n", x, counter);printf("Thread %d: Incrementing Counter...\n", x);counter++;printf("Thread %d: New Counter Value: %d\n", x, counter);printf("Thread %d: Exiting critical region...\n", x);/* END CRITICAL REGION */    sem_post(&mutex);       /* up semaphore */pthread_exit(0); /* exit thread */
}} // namespaceint main()
{int i[2];pthread_t thread_a;pthread_t thread_b;i[0] = 0; /* argument to threads */i[1] = 1;sem_init(&mutex, 0, 1);      /* initialize mutex to 1 - binary semaphore *//* second param = 0 - semaphore is local *//* Note: you can check if thread has been successfully created by checking return value ofpthread_create */                                 pthread_create(&thread_a, NULL, handler, (void*)&i[0]);pthread_create(&thread_b, NULL, handler, (void*)&i[1]);pthread_join(thread_a, NULL);pthread_join(thread_b, NULL);sem_destroy(&mutex); /* destroy semaphore *//* exit */  exit(0);
} /* main() */


GitHub: https://github.com/fengbingchun/Linux_Code_Test

相关文章:

Linux环境HBase安装配置及使用

Linux环境HBase安装配置及使用 1. 认识HBase (1) HBase介绍 HBase Hadoop database&#xff0c;Hadoop数据库开源数据库官网&#xff1a;hbase.apache.org/HBase源于Google的BigTableApache HBase™是Hadoop数据库&#xff0c;是一个分布式&#xff0c;可扩展的大数据存储。当…

适合小团队作战,奖金+招聘绿色通道,这一届算法大赛关注下?

大赛背景伴随着5G、物联网与大数据形成的后互联网格局的逐步形成&#xff0c;日益多样化的用户触点、庞杂的行为数据和沉重的业务体量也给我们的数据资产管理带来了不容忽视的挑战。为了建立更加精准的数据挖掘形式和更加智能的机器学习算法&#xff0c;对不断生成的用户行为事…

Linq 集合处理(Union)

关于Union的两种情况 一、简单值类型或者string类型处理方式(集合需要实现IEnumerable接口) #region int类型List<int> ints1 new List<int> { 1, 2, 3, 4, 5, 6 };List<int> ints2 new List<int> { 5, 6, 7, 8, 9, 0 };IEnumerable<int> ints…

卷积神经网络中十大拍案叫绝的操作

作者 | Justin ho来源 | 知乎CNN从2012年的AlexNet发展至今&#xff0c;科学家们发明出各种各样的CNN模型&#xff0c;一个比一个深&#xff0c;一个比一个准确&#xff0c;一个比一个轻量。下面会对近几年一些具有变革性的工作进行简单盘点&#xff0c;从这些充满革新性的工作…

windows7下解决caffe check failed registry.count(type) == 1(0 vs. 1) unknown layer type问题

在Windows7下调用vs2013生成的Caffe静态库时经常会提示Check failed: registry.count(type) 1 (0 vs. 1) Unknown layer type的错误&#xff0c;如下图&#xff1a;这里参考网上资料汇总了几种解决方法&#xff1a;1. 不使用Caffe的静态库&#xff0c;直接将Caffe的sourc…

js 变量提升 和函数提升

2019独角兽企业重金招聘Python工程师标准>>> 创建函数有两种形式&#xff0c;一种是函数声明&#xff0c;另外一种是函数字面量&#xff0c;只有函数声明才有变量提升 console.log(a) // f a() { console.log(a) } console.log(b) //undefinedfunction a() {consol…

.net_ckeditor+ckfinder的图片上传配置

CKEditor和CKFinder的最新版可以到官方网站&#xff08;http://cksource.com&#xff09;上下载获得。 把以上两个资源放到网站的根目录&#xff1a; /CKEditor 和 /CKFinder (不区分大小写) 在页面使用 CKEditor&#xff1a; <textarea cols"80" id"prcont…

VS2013在Windows7 64位上变慢的解决方法

重装了windows7系统&#xff0c;又重装了vs2013&#xff0c;发现在打开vs2013、编译工程及调试的时候&#xff0c;vs2013都会变的比较慢&#xff0c;参考网上资料&#xff0c;这里列出几种可能的解决方法&#xff1a; 1. 打开工具--> 选项 --> 源代码管理 --> 插件选…

Key-Value数据库:Redis与Memcached之间如何选择?

华为云分布式缓存Redis5.0和Memcached都是华为云DCS的核心产品。 那么在不同的使用场景之下&#xff0c;如何选择Redis5.0和Memcached呢&#xff1f; 就由小编为大家进行详细的数据对比分析吧Redis和Memcached都是非常受欢迎的开源内存数据库&#xff0c;相对关系型数据库&…

裴健等9名华人当选加拿大皇家学会院士

【导读】近日&#xff0c;加拿大皇家学会&#xff08;RSC&#xff0c;The Royal Society of Canada&#xff09;官网宣布已评选出今年的新增院士。其中&#xff0c;京东副总裁、加拿大西蒙弗雷泽大学计算科学学院教授裴健和其他 8 名华人学者均在这份名单之中&#xff0c;而裴健…

Linux中shell命令的用法和技巧

使用Linux shell是我每天的基本工作&#xff0c;但我经常会忘记一些有用的shell命令和l技巧。当然&#xff0c;命令我能记住&#xff0c;但我不敢说能记得如何用它执行某个特定任务。于是&#xff0c;我开始在一个文本文件里记录这些用法&#xff0c;并放在我的Dropbox里&#…

Caffe中Layer注册机制

Caffe内部维护一个注册表用于查找特定Layer对应的工厂函数(Layer Factory的设计用到了设计模式里的工厂模式)。Caffe的Layer注册表是一组键值对(key, value)( LayerRegistry里用map数据结构维护一个CreatorRegistry list, 保存各个Layer的creator的函数句柄)&#xff0c;key为L…

自动驾驶行业观察 | 停车不再难,L2到L4的泊车辅助系统技术剖析

作者 | 陈光来源 | 自动驾驶干货铺&#xff08;ID:IntelligentDrive&#xff09;【导读】在汽车智能化的浪潮中&#xff0c;车载传感器发展迅速&#xff0c;越来越多搭载了先进传感器的汽车进入了我们的视野。比如能够在高速公路上实现单车道巡航的凯迪拉克CT6&#xff0c;以及…

Unity Log重新定向

Unity Log重新定向 使用Unity的Log的时候有时候需要封装一下Debug.Log(message)&#xff0c;可以屏蔽Log或者把log内容写到文本中。通过把文本内容传送到服务器中&#xff0c;查找bug出现的原因。但是封装之后的日志系统如果双击跳转的时候&#xff0c;会跳转到自定义的日志系统…

Javascript 检查一组 radio 中的哪一个被勾选

2019独角兽企业重金招聘Python工程师标准>>> 以前检查单选按钮是否被选择时&#xff0c;我使用的是 if else 一个一个的检查其 checked 属性。 这样虽然可以&#xff0c;但是当一组 radio 有很多个时&#xff0c;就很麻烦了。 可以通过 getElementsByName 得到所有…

二维码Aztec简介及其解码实现(zxing-cpp)

Aztec Code是1995年&#xff0c;由Hand HeldProducts公司的Dr. Andrew Longacre设计。它是一种高容量的二维条形码格式。它可以对ASCII和扩展ASCII码进行编码。当使用最高容量和25%的纠错级别的時候&#xff0c;Aztec可以对3000个字符或者3750个数字进行编码。Aztec的矩阵大小在…

顶配12699 元、没有5G,“浴霸三摄”的iPhone你会买吗?

作者 | 屠敏出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;北京时间 9 月 11 日凌晨 1 点&#xff0c;以「Apple 特别活动」为主题的苹果秋季发布会正式于史蒂夫乔布斯剧院拉开帷幕。按照惯例&#xff0c;在发布会之前&#xff0c;业界“毫不留情”地对新品进行了…

阿里P7架构师告诉你Java架构师必须知道的 6 大设计原则

在软件开发中&#xff0c;前人对软件系统的设计和开发总结了一些原则和模式&#xff0c; 不管用什么语言做开发&#xff0c;都将对我们系统设计和开发提供指导意义。本文主要将总结这些常见的原则&#xff0c;和具体阐述意义。 开发原则 面向对象的基本原则(solid)是五个&#…

rhel6用centos163 yum源

cd /etc/yum.repos.d/wget wget http://mirrors.163.com/.help/CentOS6-Base-163.reposed -i "s/\$releasever/6/" CentOS6-Base-163.repo

打破深度学习局限,强化学习、深度森林或是企业AI决策技术的“良药”

算法、算力和数据是人工智能时代的三驾马车&#xff0c;成为企业赋能人工智能的动力&#xff0c;但它们自身的特性也为企业和高校在研究和落地应用过程带来了重重挑战。比如&#xff0c;训练算法的成本高昂&#xff0c;数据从采集、处理到存储已面临瓶颈&#xff0c;目前针对算…

JAVA springboot微服务b2b2c电子商务系统(十三)断路器聚合监控(Hystrix Turbine)

讲述了如何利用Hystrix Dashboard去监控断路器的Hystrix command。当我们有很多个服务的时候&#xff0c;这就需要聚合所以服务的Hystrix Dashboard的数据了。这就需要用到Spring Cloud的另一个组件了&#xff0c;即Hystrix Turbine。一、Hystrix Turbine简介看单个的Hystrix D…

二维码Data Matrix的解码实现(zxing-cpp)

二维码Data Matrix的介绍可以参考http://blog.csdn.net/fengbingchun/article/details/44279967 ,以下是通过zxing-cpp开源库实现的对Data Matrix进行解码的测试代码&#xff1a;#include "funset.hpp" #include <string> #include <fstream> #include &…

PHP mongodb 的使用

mongodb 不用过多的介绍了&#xff0c;NOSQL的一种&#xff0c;是一个面向文档的数据库&#xff0c;以其方便灵活的数据结构&#xff0c;对于开发者来说是比较友好的&#xff0c;同时查询的速度也是比较快的&#xff0c;现在好多网站 开始使用mongodb ,具体的介绍可以网上查找。…

必看,61篇NeurIPS深度强化学习论文解读都这里了

作者 | DeepRL来源 | 深度强化学习实验室&#xff08;ID: Deep-RL&#xff09;NeurIPS可谓人工智能年度最大盛会。每年全球的人工智能爱好者和科学家都会在这里聚集&#xff0c;发布最新研究&#xff0c;并进行热烈探讨&#xff0c;大会的技术往往这未来几年就会演变成真正的研…

07-09-Exchange Server 2019-配置-Outlook 2019

[在此处输入文章标题] 《系统工程师实战培训》 -07-部署邮件系统 -09-Exchange Server 2019-配置-Outlook 2019 作者&#xff1a;学 无 止 境 QQ交流群&#xff1a;454544014 MSUCDemo01 MSUCDemo02 MSUCDemo03 MSUCDemo04 MSUCDemo05 启用邮箱 MSUCDemo01i-x-Cloud.com MSUCDe…

二维码QR Code简介及其解码实现(zxing-cpp)

二维码QR Code(Quick Response Code)是由Denso公司于1994年9月研制的一种矩阵二维码符号&#xff0c;它具有一维条码及其它二维条码所具有的信息容量大、可靠性高、可表示汉字及图象多种文字信息、保密防伪性强等优点。二维码QR Code呈正方形&#xff0c;常见的是黑白两色。在3…

jQuery学习(一)

因为项目需要&#xff0c;同时也因为兴趣&#xff0c;在近一段时间研究和使用了jQuery&#xff0c;它真的是太强大了&#xff0c;代码非常的优雅和简洁&#xff0c;好后悔现在才开始了解它&#xff0c;虽然目前网络上关于jQuery的资料、学习心得&#xff0c;教程多得你看不完&a…

知乎算法团队负责人孙付伟:Graph Embedding在知乎的应用实践

演讲嘉宾 | 孙付伟出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;9月6-7日&#xff0c;在由CSDN主办的2019中国AI开发者大会&#xff08;AI ProCon 2019&#xff09;的 机器学习专场中&#xff0c;知乎算法团队负责人孙付伟在机器学习专场中分享了《Graph Embedding…

一维码Codabar简介及其解码实现(zxing-cpp)

一维码Codabar&#xff1a;由4条黑色线条&#xff0c;3条白色线条&#xff0c;合计7条线条所组成&#xff0c;每一个字元与字元之间有一间隙Gap做区隔。条形码Codabar包含21个字元&#xff1a;(1)、10个数字0~9;(2)、””, ”-”,”*”, ”/”, ”$”, .”, ”:”等7个特殊符号…

node 压缩模块速成

1. 压缩与解压缩处理可以使用zlib模块进行压缩及解压缩处理,压缩文件以后可以减少体积&#xff0c;加快传输速度和节约带宽 代码2. 压缩对象压缩和解压缩对象都是一个可读可写流方法说明zlib.createGzip返回Gzip流对象&#xff0c;使用Gzip算法对数据进行压缩处理zlib.createGu…