【C语言·Linux】多线程基础
了解Linux系统下的进程和线程:
对于线程而言,两条线程共用一个内存,操作的是同一个内存单元。
对于这两个线程,p1和p2通用同一个a,所以两次加法操作之后,可能得到的是12,但是也不能完全确定,至于为什么,看到后面就明白了。
对于进程而言,子进程只是复制了父进程的代码,它们拥有不同的两个内存。
对于两个进程,父子进程分别占用一个a,分别操作,互不影响。
void * func(void *args){printf("hello world\n");return NULL;}int main(){pthread_t th1, th2;pthread_create(&th,NULL,func,NULL);return 0;}
这里用到pthread_create函数,第一个参数是线程变量,第三个参数是将要开启的进程,该进程返回void*类型,参数也是void*类型。
编译一下程序,发现编译错误,原来是变量th1写错了,更正一下即可。
通过tree命令可以看到,已经变成生成了test可执行文件,现在来执行该文件。
执行的结果也许并非如你所愿,因为什么东西都没有输出。这是为什么呢?
在main这个进程中,开辟了一个线程th,开辟之后,main函数继续执行,可能在th执行printf之前就已经结束。main函数的结束,将会使得其他的所有子线程都结束,导致th线程还没有执行printf就结束了。
在main函数结束之前,先等待线程结束,这样就不会出现上述问题。
这次执行,不出所料,得到了正确的输出。
上述案例只是一个线程,下面加一个线程。
void * func(void *args){int i;for(i=1; i<100; i++){printf("%5d", i);}return NULL;}int main(){pthread_t th1, th2;pthread_create(&th1,NULL,func,NULL);pthread_create(&th2,NULL,func,NULL);pthread_join(th1,NULL);pthread_join(th2,NULL);printf("\n");return 0;}
从输出的结果来看,两个线程在运行的时候,并没有先后顺序,也就是说,两个线程自己在强资源,争着输出。
如果你不信,修改一下代码,可以将两个线程区分开来。
void * func(void *args){int i;char* name=(char*)args;for(i=1; i<1000; i++){printf("%s=%d ",name, i);}return NULL;}int main(){pthread_t th1, th2;pthread_create(&th1,NULL,func,"th1");pthread_create(&th2,NULL,func,"th2");pthread_join(th1,NULL);pthread_join(th2,NULL);printf("\n");return 0;}
如果测试的效果不是很明显,你可以将1000改为1000000或者更大的数字。
假设生成5000个随机数,然后利用两个线程来完成加法运算。
一个线程完成前面2500个数字的运算,后面一个线程完成后面2500个数字的运算。
typedef struct {int first;int last;}MY_ARGS;int a[5000];void * func(void *args){int i;int s=0;MY_ARGS *my_args = (MY_ARGS*)args;for(i=my_args->first; i<my_args->last; i++){s = s + a[i];}printf("%d\n",s);return NULL;}int main(){int i;for(i=0; i<5000; i++){a[i]=rand() % 50;//伪随机数}pthread_t th1, th2;MY_ARGS args1={0,2500};MY_ARGS args2={2500,5000};pthread_create(&th1,NULL,func,&args1);pthread_create(&th2,NULL,func,&args2);pthread_join(th1,NULL);pthread_join(th2,NULL);printf("\n");return 0;}
从运行的结果可以看到,已经分别计算出了前后两部分的结果。
如果想要func函数返回数值,好像不是很方便,那就可以重新构造一下结构体。
typedef struct {int first;int last;int result;}MY_ARGS;int a[5000];void * func(void *args){int i;int s=0;MY_ARGS *my_args = (MY_ARGS*)args;for(i=my_args->first; i<my_args->last; i++){s = s + a[i];}my_args->result = s;return NULL;}int main(){int i;for(i=0; i<5000; i++){a[i]=rand() % 50;}pthread_t th1, th2;MY_ARGS args1={0,2500,0};MY_ARGS args2={2500,5000,0};pthread_create(&th1,NULL,func,&args1);pthread_create(&th2,NULL,func,&args2);pthread_join(th1,NULL);pthread_join(th2,NULL);printf("%d, %d\n",args1.result, args2.result);return 0;}
用这种方式,同样可以返回结果。
如果用全局变量的方式来实现呢?
typedef struct {int first;int last;}MY_ARGS;int a[5000];int s=0;void * func(void *args){int i;MY_ARGS *my_args = (MY_ARGS*)args;for(i=my_args->first; i<my_args->last; i++){s = s + a[i];}return NULL;}int main(){int i;for(i=0; i<5000; i++){a[i]=rand() % 50;}pthread_t th1, th2;MY_ARGS args1={0,2500};MY_ARGS args2={2500,5000};pthread_create(&th1,NULL,func,&args1);pthread_create(&th2,NULL,func,&args2);pthread_join(th1,NULL);pthread_join(th2,NULL);printf("%d\n",s);return 0;}
这个结果对不对?
用全局变量需要注意哪些问题?
int s=0;void * func(void *args){int i;for(i=0; i<1000000; i++){s ++;}return NULL;}int main(){pthread_t th1, th2;pthread_create(&th1,NULL,func,NULL);pthread_create(&th2,NULL,func,NULL);pthread_join(th1,NULL);pthread_join(th2,NULL);printf("%d\n",s);return 0;}
运行结果显示,每次运行的结果都不相同,这是为什么,为什么结果不是都等于2000000?按理说,两个线程分别执行1000000次,结果应该是两倍可惜结果不是。
当两个线程在执行的时候,th1完成读操作还没完成写操作的时候,th2可能进行了读操作,那么th2读到的数值,就是s,而当th1完成写操作的时候,值变成了s+1,假设此时th2也进行写操作,同样也是s+1,导致两次加法,第一次加法的结果被第二次的结果覆盖。当执行此时很多的时候,这种差异就变得很明显。
如何处理这种问题?
锁是一种解决方案。
pthread_mutex_t lock;//define lockint s=0;void * func(void *args){int i;for(i=0; i<1000000; i++){pthread_mutex_lock(&lock);s ++;pthread_mutex_unlock(&lock);}return NULL;}int main(){pthread_t th1, th2;pthread_mutex_init(&lock,NULL);//initialize lockpthread_create(&th1,NULL,func,NULL);pthread_create(&th2,NULL,func,NULL);pthread_join(th1,NULL);pthread_join(th2,NULL);printf("%d\n",s);return 0;}
加锁之后运行,就不会发生错误。
看一下运行时间,其实还是比较慢的,这个过程中,不断地上锁解锁,非常耗费时间。锁的位置,直接关系到程序执行的效率。
再写一种实现方式
typedef struct {int first;int last;int id;}MY_ARGS;int a[5000];int result[2];void * func(void *args){int i;MY_ARGS *my_args = (MY_ARGS*)args;for(i=my_args->first; i<my_args->last; i++){result[my_args->id] = result[my_args->id] + a[i];}return NULL;}int main(){int i;for(i=0; i<5000; i++){a[i]=rand() % 50;}pthread_t th1, th2;MY_ARGS args1={0,2500,0};MY_ARGS args2={2500,5000,1};pthread_create(&th1,NULL,func,&args1);pthread_create(&th2,NULL,func,&args2);pthread_join(th1,NULL);pthread_join(th2,NULL);printf("%d\n",result[0]+result[1]);return 0;}
可以与前面使用result的方法进行效率对比,这里存在一个“假共享”的问题。
在result数组中,计算机为了加快读取速度,会将一批连续的存储单元读入CPU,这导致两个线程在读取内容并修改内容之后,RAM中的内容与CPU中的不一致,RAM又要重新读取更新,导致耗费更多的时间。
为了解决这样的问题,一个方式就是将result[2]改为result[101],也就是将数组扩大,第一个值存在result[0],另一个值存在result[100]。
int a[5000];int result[101];void * func(void *args){int i;MY_ARGS *my_args = (MY_ARGS*)args;for(i=my_args->first; i<my_args->last; i++){result[my_args->id * 100] = result[my_args->id * 100] + a[i];}return NULL;}
为什么将两个数值分开就不会出现“假共享”?
在CPU复制RAM的内容时,不会一次性复制很多个,当两个值距离比较远的时候,就不会受到影响。
