看到一个被推荐的热贴: 对于“多线程访问同一个变量是否需要加锁”的研究 得出的结论是:
1.对于int,short,char,BOOL等小于等于4字节的简单数据类型,如果无逻辑上的先后关系,多线程读写可以完全不用加锁
http://topic.csdn.net/u/20101217/14/05CB2E93-03E9-4F77-BE65-9241393C1DF3.html
对原作者的研究精神可佳, 但得出的结论不敢苟同. 原因是:
1) 原作者测试用例不具代表性, 不能证明写入的与读出的一致.
两个线程写两个不同的值, 一个读线程. 读线程仅判断是不否两个写入值之一.
** 并不能说明是读出的就是最后写入的值, 只能证明曾经写入过这个值 **. 实际应用中, 如计数 i++, 在被变量写入前还隐含了对变量的读操作.
这里加锁的目的: 是保证其它线程的写入的值被正确的同步到本线程的 cache 里.
同时保证本线程写入的值会被正确同步到其它线程的 cache 里.2) 在这样一个测试用例里得出相反的结果 (Windows XP / VC++):
用例1 (伪代码):
int g_num; /* 共享变量 */
void sub_thread(int * pnum)
{
int i;
for (i=0; i<500000; i++)
{
(*pnum) ++;
g_num++;
}
}
int main()
{
int num1; /* 统计线程 1 运行的次数 */
int num2; /* 统计线程 2 运行的次数 */
/* 创建两个线程 */
tid1 = thread_create(sub_thread, (void *)&g_num1);
tid2 = thread_create(sub_thread, (void *)&g_num2);
/* 等待线程结束 */
wait(&tid1);
wait(&tid2);
/* 输出公共变量 和各线程的计数 */
printf("g_num: %d, num1: %d, num2: %d\n", g_num++, num1, num2);
return 0;
} 运行结束(5次):
g_num: 947993, num1: 500000, num2: 500000
g_num: 959853, num1: 500000, num2: 500000
g_num: 874089, num1: 500000, num2: 500000
g_num: 965716, num1: 500000, num2: 500000
g_num: 796037, num1: 500000, num2: 500000
用例2 (伪代码):
int g_num;
void sub_thread()
{
g_num++; /* 未加锁的读/写 */
}
int main()
{
/* 创建一个拥有 10 个子线程的线程池 */
pool = create_thread_pool(10);
/* 向线程池里添加任务 500000 个任务, 由 10 个子线程并完成 */
for (i=0; i<500000; i++)
{
/* 向线程池里添加任务 */
threadpool_jobadd(pool, subthread);
}
/* 销毁线程池 */
destroy_thread_pool(pool);
printf("g_num: %d\n", g_num++);
}
运行十次结果:
1. g_num: 499958
2. g_num: 499949
3. g_num: 499983
4. g_num: 499986
5. g_num: 499932 (对于加锁后的用例就不列出了)结论:
1) 显然不加锁累加结果并不预期的值.
2) 对于int,short,char,BOOL等小于等于4字节的简单数据类型访问, 依然需要进行加锁.
1.对于int,short,char,BOOL等小于等于4字节的简单数据类型,如果无逻辑上的先后关系,多线程读写可以完全不用加锁
http://topic.csdn.net/u/20101217/14/05CB2E93-03E9-4F77-BE65-9241393C1DF3.html
对原作者的研究精神可佳, 但得出的结论不敢苟同. 原因是:
1) 原作者测试用例不具代表性, 不能证明写入的与读出的一致.
两个线程写两个不同的值, 一个读线程. 读线程仅判断是不否两个写入值之一.
** 并不能说明是读出的就是最后写入的值, 只能证明曾经写入过这个值 **. 实际应用中, 如计数 i++, 在被变量写入前还隐含了对变量的读操作.
这里加锁的目的: 是保证其它线程的写入的值被正确的同步到本线程的 cache 里.
同时保证本线程写入的值会被正确同步到其它线程的 cache 里.2) 在这样一个测试用例里得出相反的结果 (Windows XP / VC++):
用例1 (伪代码):
int g_num; /* 共享变量 */
void sub_thread(int * pnum)
{
int i;
for (i=0; i<500000; i++)
{
(*pnum) ++;
g_num++;
}
}
int main()
{
int num1; /* 统计线程 1 运行的次数 */
int num2; /* 统计线程 2 运行的次数 */
/* 创建两个线程 */
tid1 = thread_create(sub_thread, (void *)&g_num1);
tid2 = thread_create(sub_thread, (void *)&g_num2);
/* 等待线程结束 */
wait(&tid1);
wait(&tid2);
/* 输出公共变量 和各线程的计数 */
printf("g_num: %d, num1: %d, num2: %d\n", g_num++, num1, num2);
return 0;
} 运行结束(5次):
g_num: 947993, num1: 500000, num2: 500000
g_num: 959853, num1: 500000, num2: 500000
g_num: 874089, num1: 500000, num2: 500000
g_num: 965716, num1: 500000, num2: 500000
g_num: 796037, num1: 500000, num2: 500000
用例2 (伪代码):
int g_num;
void sub_thread()
{
g_num++; /* 未加锁的读/写 */
}
int main()
{
/* 创建一个拥有 10 个子线程的线程池 */
pool = create_thread_pool(10);
/* 向线程池里添加任务 500000 个任务, 由 10 个子线程并完成 */
for (i=0; i<500000; i++)
{
/* 向线程池里添加任务 */
threadpool_jobadd(pool, subthread);
}
/* 销毁线程池 */
destroy_thread_pool(pool);
printf("g_num: %d\n", g_num++);
}
运行十次结果:
1. g_num: 499958
2. g_num: 499949
3. g_num: 499983
4. g_num: 499986
5. g_num: 499932 (对于加锁后的用例就不列出了)结论:
1) 显然不加锁累加结果并不预期的值.
2) 对于int,short,char,BOOL等小于等于4字节的简单数据类型访问, 依然需要进行加锁.
查看下volatile这个关键字的使用方法,就明白为啥简单数据类型也不能保证所谓的原子操作。