多线程同步的锁:互斥锁和读写锁

在 -maimai 上读到去阿里的同学考察多线程的锁问题,想起早几年写的一个用Java实现的Session Server(现在还在服役中),起初是基于Java的Synchronized用在方法和对象上,后来改由ConcurrentHashMap做容器。参照 -ChinaUnix 上,ifndef 给例子(-R/92TQ ),我重复了一下这个实验,算是对互斥锁和读写锁的一种回顾。

概念,“线程同步简单的说就是当多个线程共享相同的内存时,当某个线程可以修改变量,而其他线程也可以读取或修改这个变量的时候,就需要对这些线程进行同步,以确保他们在访问变量的存储内容时不会访问到无效的数值。”,确保同步的措施是在操作前进行加锁。

互斥锁, “我们可以把互斥量想成一把锁。在访问共享资源前 对互斥量进行加锁,在访问完后释放互斥量上的锁。
对互斥量进行加锁后,任何其他试图再对该互斥量加锁的线程都会被阻塞直到当前持有锁的线程释放锁。”

读写锁, “当读写锁是写加锁时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。
当读写锁是读加锁时,在这个锁被解锁之前,所有试图以读模式对他进行加锁的线程都可以得到访问权”。

根据如上描述,以及Java SDK中对 Synchronized和ConcurrentHashMap的描述,

Synchronized,“ it is not possible for two invocations of synchronized methods on the same object to interleave. When one thread is executing a synchronized method for an object, all other threads that invoke synchronized methods for the same object block (suspend execution) until the first thread is done with the object.”(-R/P2Tc )

ConcurrentHashMap,” even though all operations are thread-safe, retrieval operations do not entail locking, and there is not any support for locking the entire table in a way that prevents all access. ”(-R/22Uk ) …“It uses a multitude of locks, each lock controls one segment of the hash. When setting data in a particular segment, the lock for that segment is obtained.”(-R/b2Tc )

从上文的描述来看,Synchronized的实现类似互斥锁,而ConcurrentHashMap的实现类似于读写锁。

实际的例子,若多线程不加锁,运行的结果将很异常,如下程序:

/*
 * pthread mutex lock, rwlock test
* wadelau@ufqi.com, Mon Apr  4 21:32:27 CST 2016
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
int i = 0;
static int COUNT_NUM = 1000000;
static void *thread_1();
static void *thread_2();
int main(void){
int err;
pthread_t th1, th2;
err = pthread_create(&th1, NULL, thread_1, (void *)0);
if(err != 0){
printf(“Create new thread error: %s\n”, strerror(err));
exit(0);
}
err = pthread_create(&th2, NULL, thread_2, (void *)0);
if(err != 0){
printf(“Create new thread error: %s\n”, strerror(err));
exit(0);
}
err = pthread_join(th1, NULL);
if( err != 0){
printf(“Wait thread done error: %s\n”, strerror(err));
exit(0);
}
err = pthread_join(th2, NULL);
if( err != 0){
printf(“Wait thread done error: %s\n”, strerror(err));
exit(0);
}
printf(“i=%d\n”, i);
exit(0);
}
static void *thread_1(void *args){
int j;
for(j=0; j<COUNT_NUM; j++){
i++;
}
pthread_exit((void *)0);
}
static void *thread_2(void *args){
int j;
for(j=0; j<COUNT_NUM; j++){
i++;
}
pthread_exit((void *)0);
}

编译时,使用:

shell> gcc -lpthread xxx.c  -o xxx.out

程序运行十次的结果各不相同:

shell> for i in {0..9}; do ./pthread_nolock.out; done;

i=1042904
i=1064909
i=1026858
i=1038045
i=1133655
i=1085719
i=1029224
i=1089466
i=1020473
i=1041702

对线程使用互斥锁:

static int COUNT_NUM = 1000000;
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
err = pthread_create(&th1, NULL, thread_1, (void *)0);
pthread_mutex_destroy(&mutex);
printf(“i=%d\n”, i);
for(j=0; j<COUNT_NUM; j++){
pthread_mutex_lock(&mutex);
i++;
pthread_mutex_unlock(&mutex);
}

 

再次运行的结果:

shell> for i in {0..9}; do ./pthread_mutex.out ; done;
i=2000000
i=2000000
i=2000000
i=2000000
i=2000000
i=2000000
i=2000000
i=2000000
i=2000000
i=2000000

已经完全符合预期了。 再对线程使用读写锁。代码变化:

static int COUNT_NUM = 1000000;
//pthread_mutex_t mutex;
pthread_rwlock_t rwlock;
//pthread_mutex_init(&mutex, NULL);
pthread_rwlock_init(&rwlock, NULL);
err = pthread_create(&th1, NULL, thread_1, (void *)0);
//pthread_mutex_destroy(&mutex);
pthread_rwlock_destroy(&rwlock);
printf(“i=%d\n”, i);
 volatile int a;
 for(j=0; j<COUNT_NUM; j++){
        pthread_rwlock_rdlock(&rwlock);
        a = i;
        pthread_rwlock_rdlock(&rwlock);
 }

再次运行程序,并计时,看看读写锁的程序时间:

shell> for i in {0..9}; do time ./pthread_rwlock_rd.out ; done;
i=1000000
real    0m0.534s
user    0m0.576s
sys     0m0.460s
i=1000000
real    0m0.333s
user    0m0.392s
sys     0m0.232s
i=1000000
real    0m0.282s
user    0m0.340s
sys     0m0.180s
i=1000000
real    0m0.336s
user    0m0.340s
sys     0m0.288s
i=1000000
real    0m0.278s
user    0m0.324s
sys     0m0.188s
i=1000000
real    0m0.224s
user    0m0.268s
sys     0m0.124s
i=1000000
real    0m0.257s
user    0m0.272s
sys     0m0.192s
i=1000000
real    0m0.261s
user    0m0.296s
sys     0m0.176s
i=1000000
real    0m0.258s
user    0m0.256s
sys     0m0.212s
i=1000000
real    0m0.366s
user    0m0.420s
sys     0m0.272s

 

同时运行使用互斥锁程序并计时,数据如下:

shell> for i in {0..9}; do time ./pthread_mutex_rd.out ; done;
i=2000000
real    0m0.178s
user    0m0.168s
sys     0m0.140s
i=2000000
real    0m0.183s
user    0m0.176s
sys     0m0.140s
i=2000000
real    0m0.177s
user    0m0.188s
sys     0m0.120s
i=2000000
real    0m0.183s
user    0m0.168s
sys     0m0.152s
i=2000000
real    0m0.198s
user    0m0.152s
sys     0m0.196s
i=2000000
real    0m0.168s
user    0m0.124s
sys     0m0.160s
i=2000000
real    0m0.155s
user    0m0.160s
sys     0m0.100s
i=2000000
real    0m0.165s
user    0m0.148s
sys     0m0.136s
i=2000000
real    0m0.160s
user    0m0.140s
sys     0m0.132s
i=2000000
real    0m0.136s
user    0m0.152s
sys     0m0.080s

 

可以发现,读写锁比互斥锁的运行时间要长。然而也应该辩证的看,在一些写操作比较多或是本身需要同步的地方并不多的程序中我们应该使用互斥量,而在读操作远大于写操作的一些程序中我们应该使用读写锁来进行同步。

进一步地,在前文所述的 Session Server中,由使用互斥锁的Synchronized的更改为使用读写锁(读不上锁,写部分区间上锁)的ConcurrentHashMap,算是某种优化。

最后,想陈述的是某种编码风格,code style:

1) 逻辑区块用单行隔开,single blank line for every block
code-style-201604

2)功能区块用双空格,double blank lines for every function block
code-style-201604-002

3)左花括号 { 随上行尾部,右花括号 } 换行顶格, else, else if 换新行.

4)注释要写,能用English or pinyin,不用其他语言。

5)能不写常量字符,尽量不要写。从一开始就这么老师要求学生,每每总是抑制不住写一些常量值。

One more,起初重复运行程序十遍,我用了Awk,没有原因,就是顺手:
shell> echo “” | awk ‘{print “time ./pthread_mutex_rd.out “;}’ |sh

发表在 编程技术, 计算机技术 | 标签为 , , , , , | 留下评论

愛自己悅己容2nd愛自己悅己容汽車老司機車載汽車老司機

-gMIS更新: 自动层级目录

-gMIS 平台层级结构管理功能模块(Xdirectory)终于在 @Tintin 的帮助下,顺利完成。 Xdirectory是为了方便用户输入树形的数据结构,如下图所示: gmis-xdirectory-201604

通过xdirectory定义树形结构,可以方便的展示类似widnows资源管理器样式的菜单。

所谓层级(hierarchy)、分类(classification)及编码(encode)、代码(code)的含义,可以进一步参考 -ufqi-blog 之前的专页讨论:-R/k2Sl  。 Xdirectory有两个主要组成部分: extra/xdirectory.php 和 class/xdirectory.class.php . 前者负责与 -gMIS 系统的集成,后者负责具体编码及层级的实现。

1) extra/xdirectory.php 负责将层级代码list按一定的缩进规则呈现出来,并响应相应的选择、增加同级和增加下级的点击事件,将相应结果抄送到调用者那里。

2)class/xdirectory.class 主要有三个方法,负责生产list,生产同级代码和生产下级代码。 # get dir list, expand all of directories above the target dir or its same level, “open all to target”
function getList($targetDir, $levelLen){ } # get next dir code, 2c after 2b, 10 after 0z, in base36, i.e. 0-9, a-z
function getNextDir($currentDir, $levelLen, $ilevel, $currentVal){ } # get sub dir under currentDir, a1b200 for a1b2
function getSubDir($currentDir, $levelLen, $ilevel, $currentVal){ }

Xdirectory 默认的层宽度是两位,默认采用的编码是36进制,也即,0-9, a-z , 通常情况下,每层级可以容纳  1296 个元素(36^2, 00…..0z, 10….1z…..zz),能够满足一般情况下的分类分级需求。更大的层宽度,可以考虑3位(46,656个)。

-gMIS 在设计的时候,预留了扩展输入/输出接口,在 extra 目录下,通过表配置中的filed的 inputtype可以指定某个字段的输入类型为 extra 扩展输入,调用 xdirectory 则是 <extrainput>extra/xdirectory.php?tbl=THIS_TABLE&amp;icode=icode&amp;iname=iname&amp;parentcode=THIS_icode</extrainput> Xdirectory 首先被用在 -gMIS 的菜单项管理中,如下图是配置顶部导航菜单的功能模块。 -gmis-menu-setting  

发表在 -gMIS, 编程技术, 计算机技术 | 标签为 , , , | 2条评论

中国国民党Apache Softwares亮月亮科技OpenAI ChatGPT

-GWA2 core updates: 容错处理,孤儿占位符

-GWA2 的核心升级了一处容错处理,问题描述为:

如果某个WebApp的实例$obj, 在进行SQL组装时,如果$obj->set 的变量数据与SQL中的占位符(?)不一致时,特别是占位符(?)所指代的变量名未出现时,在SQL拼装时,容易产生将idxArr[0]的值赋予给这个缺失的变量值。这样就导致占位符序列和变量序列错位现象,从而可能到SQL在写入时的错误。这种错误可称之为“孤儿占位符”。

问题的原因是,调用者在使用$obj->set时,在SQL中写入了某个占位符,但并未在$obj-hmf中给定相应的值。一个错误的调用如:
$obj->set(‘b’, $aVal);
$obj->set(‘c’, $bVal);
$result = $obj->setBy(‘a, b, c’, null);
此时的a,没有通过$obj->set赋值,会在后续拼装SQL时出错。

解决的办法是,在拼装匹配占位符和变量时,做变量名和变量值的检查。涉及到的代码在 inc/mysql.class , 如下:

if(strpos($t, $idxarr[$i]) === false){
# in case that, field was not set by $obj->set but written in sql with ‘?’, Sat Apr 2 23:54:48 CST 2016
debug(__FILE__.”: found unmatched field:[$t].”);
$sql = substr($sql,$a+1);
$a = strpos($sql,”?”);
$newsql .= str_replace(“?”, ‘\’\”, $t);
}
else{
$sql = substr($sql,$a+1);
$a = strpos($sql,”?”);
$newsql .= str_replace(“?”,$this->_QuoteSafe($hmvars[$idxarr[$i]]),$t);
$i++;
}

同时也将修改同步到基于 -GWA2 的 -gMIS 中。

发表在 -GWA2, 编程技术, 计算机技术 | 标签为 , , , | 留下评论

書中自有顔如玉愛自己悅己容2nd愛自己悅己容汽車老司機車載

GWA2更新:SQL语句写入数字型字符串(2)

关于在MySQL的使用中,插入数字型字符串时,在贴文:
“GWA2更新:SQL语句写入数字型字符串” (  -R/gwa2-update  )
做过分析,遇到这样的情况,需要特殊对待,避开 $obj->setBy , 而是使用 $obj->execBy 来自行拼凑SQL语句。

然而在 -gMIS 这样的使用情景中,却不能自行拆解 $obj->setBy , 所以需要进一步的分析寻找解决方法,思路如下:
1. -gMIS 环境中,有 tblconf.inc 通过他能够读取到字段的默认值,这也是在
“-gMIS更新兼容Strict SQL Mode” (  -R/e2TX  )
讨论过,设想如果知道一个字段的默认值,可以据此判断它的值的表现形式;这是下面的基础;
2. 在 webapp.class 中增加 $hmfieldinfo 用于保存当前操作表的结构信息,主要包括字段默认值等信息;从这里也可以读取到字段类型,比如int,char等;后续可以继续利用这些属性;
3. 进一步地,在 gtbl.class extends webapp.class 中,在设置 setFieldList 时,填充 $hmfieldinfo ;
4. 在dba.class 中,进一步的透传 $hmfieldinfo 到 mysql.class 中,然后在 query执行过程中,根据 $hmfieldinfo 中的相关属性进行判断:当field是char或者默认值是 ” 时,即便是 0102 这样的值,也当作字符串进行处理。

如此即可。

发表在 -gMIS, -GWA2, 编程技术, 计算机技术 | 标签为 , | 留下评论

好奇婴幼儿中国国民党革委会中国国民党Apache Softwares

在office word中插入程序源代码/insert source codes in office word

最近在整理关于 -gMIS 和 -GWA2 的相关手册,使用MS office word 进行相关文档的编写,发现需要在Word中插入排版整齐,带有语法高亮的源程序代码是一种迫切的需求,经搜索找到英文版的操作指南,于是尝试也参照写一份中文的,在Office Word中插入排版整齐、带有语法高亮源程序代码的操作步骤.

1. 准备工作
打开Office word,打开Notepad++ ( 注意!不是WIndows 自带的Notepad,而是 -Notepad++ ),打开要插入的源程序代码在 -Notepad++ 中,备用。

2. 将源代码从Notepad++中带格式复制出来
source-code-in-word-201602

3. 在Office Word中插入一个 Text Box(文本框),然后将剪贴板中的带格式的源代码复制进来
插入一个文本框(text box):
source-code-in-word-201602-3

在文本框中,右键选择粘贴(Paste):

source-code-in-word-201602-2
事情就这样成了。

4. 某些情况下,还需要选中粘贴进来的文本,去掉文本背景色,以实现对代码原格式的呈现。

 

发表在 -gMIS, -GWA2, 计算机技术 | 标签为 , , | 留下评论

好奇婴幼儿中国国民党革委会中国国民党Apache Softwares

万恶之源的《商君书》

商君书制民五法.201602 中国万恶之源,秦朝商鞅的《商君书》。

胡紫微 讲得太好了!真的精彩。句句中的。而且我赌你能听懂。。值得花一个半小时去聆听的一次讲课。诚荐。//@于晓非微博: 再转!强烈推荐观看鲍鹏山老师的精彩演讲。鲍老师彻底剖析了中国两千多年黑暗专制政治的理论基础! @于晓非微博 强烈推荐 鲍鹏山教授讲《商君书》!(视频)这是一堂刨了中国两千多年专制统治制度祖坟的讲座!(-R/P2SO  )

-R/E2SI , 令人脊背发凉的历史评说,中国专制社会理论基础,国强民弱。 中华民族五千年黑暗史的源头,中国万恶社会之源,商鞅的《商君书》。 通过专制统治人民的五种方法: 1.以弱去强,以奸驭良,实行流氓政治、小人政治; 2. 壹教,思想统治; 3.剥夺个人资产,造成一个无恒产、无恒心的社会; 4.辱民、贫民、弱民,以利于统治; 5.杀力,发动战争,外杀强敌,内杀强民。        

 

发表在 社会生活 | 标签为 , , , , | 3条评论

書中自有顔如玉愛自己悅己容2nd愛自己悅己容汽車老司機車載