目录
一、 数据共享
数据指的是全局、静态、堆区、文件描述符等。
#include<stdio.h>
#include<pthread.h>
#include<string.h> //strtok()
#include<assert.h> //assert()
/*thr_fun1线程切割线程内的字符串*/
void* thr_fun1(void* arg)
{
char buf[] = "a b c d";
sleep(2);
char* p = strtok(buf, " ");
while(NULL != p)
{
printf("thr_fun pthread: %s\n", p);
sleep(1);
p = strtok(NULL, " ");
}
}
/*理想中main主线程切割主线程的字符串*/
int main()
{
pthread_t tid;
int res = pthread_create(&tid, NULL, thr_fun1, NULL);
assert(0 == res);
char buf[] = "0 1 2 3 4 5 6 7 8 9 ";
char* p = strtok(buf, " ");
while(NULL != p)
{
printf("main pthread: %s\n", p);
sleep(2);
p = strtok(NULL, " ");
}
pthread_join(tid, NULL);
}
初看之下,main主线程和thr_fun1函数线程之间并没有共享数据,但是strtok内部实现时,会定义一个静态变量来记录下一次的开始位置。正是因为局部静态变量的存在,所以切割字符串就存在问题。理想情况下, main主线程一次输出0 1 2 3 4 5 6 7 8 9 ,thr_fun1函数线程依次输出a b c d,并发的含义是两个线程会交替执行,但是只看单个线程而言,运行是从上一次暂停的位置继续向下执行的。
图1 告诉我们strtok函数在多线程情况下是不安全的,但是运行结果不一定就是非要和预想中的结果不同(全凭天意)。不能把希望寄托在程序这次能够被正确的执行不出错,我们要求的是,任何环境下都要百分百正确!
二、 语句不具有原子性
源文件经过预编译、编译、汇编、链接生成可执行文件。编译过程会生成汇编指令,而汇编过程是根据汇编指令和机器指令的对照表意义翻译。关键在于汇编指令是一条条去执行的,但是一条语句可能对应多条汇编指令。就有可能导致一条语句在几个时刻才能执行完。
在多核环境下,线程与线程间是并行运行。如图2 所示,变量a的初始值为0。线程1和线程2都对a执行10000次后置++,我们预想a的值会变成20000,而实际上a的值∈[10 000,20 000],何解?线程1先执行指令1和指令2,寄存器中的值为1;线程2顺利执行3条指令,将内存中的值改为1;线程1现在执行指令3,取出eax寄存器中的值放入内存中,a的值还是1。两个线程以此顺序执行10000次,a的值变为10000,这是最小值的情况。
#include<stdio.h> //printf()
#include<pthread.h>
#include<assert.h> //assert()
#include<unistd.h>
void* thr_fun1(void* arg) //函数线程
{
int *p = arg;
for(int i = 0; i < 10000; ++i)
{
*p += 1;
}
}
int main() //主线程
{
pthread_t tid;
int a = 0;
int res = pthread_create(&tid, NULL, thr_fun1, (void*)&a);
assert(0 == res);
for(int i = 0; i < 10000; ++i)
{
a += 1;
a += 2;
}
pthread_join(tid, NULL); //等函数线程运行结束,主线程才能输出a的值。
printf("a = %d\n", a);
}
如图3 所示,预期的结果是40000,但是连续几次运行程序,虽然结果以40000居多——根源是因为指令执行速度太快,3条汇编指令很少会被打断。但是也会出现反常的结果,分析过程和图2 类似。