vlambda博客
学习文章列表

【C语言·Linux】多线程基础

了解Linux系统下的进程和线程:

对于线程而言,两条线程共用一个内存,操作的是同一个内存单元。

对于这两个线程,p1和p2通用同一个a,所以两次加法操作之后,可能得到的是12,但是也不能完全确定,至于为什么,看到后面就明白了。

对于进程而言,子进程只是复制了父进程的代码,它们拥有不同的两个内存。

【C语言·Linux】多线程基础

对于两个进程,父子进程分别占用一个a,分别操作,互不影响。

#include <stdio.h>#include <stdlib.h>#include <pthread.h>
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*类型。

【C语言·Linux】多线程基础

编译一下程序,发现编译错误,原来是变量th1写错了,更正一下即可。

【C语言·Linux】多线程基础

通过tree命令可以看到,已经变成生成了test可执行文件,现在来执行该文件。

【C语言·Linux】多线程基础

执行的结果也许并非如你所愿,因为什么东西都没有输出。这是为什么呢?

【C语言·Linux】多线程基础

在main这个进程中,开辟了一个线程th,开辟之后,main函数继续执行,可能在th执行printf之前就已经结束。main函数的结束,将会使得其他的所有子线程都结束,导致th线程还没有执行printf就结束了。

【C语言·Linux】多线程基础

在main函数结束之前,先等待线程结束,这样就不会出现上述问题。

【C语言·Linux】多线程基础

这次执行,不出所料,得到了正确的输出。

【C语言·Linux】多线程基础


上述案例只是一个线程,下面加一个线程。

#include <stdio.h>#include <stdlib.h>#include <pthread.h>
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;}

【C语言·Linux】多线程基础

从输出的结果来看,两个线程在运行的时候,并没有先后顺序,也就是说,两个线程自己在强资源,争着输出。

如果你不信,修改一下代码,可以将两个线程区分开来。

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个随机数,然后利用两个线程来完成加法运算。

【C语言·Linux】多线程基础

一个线程完成前面2500个数字的运算,后面一个线程完成后面2500个数字的运算。

#include <stdio.h>#include <stdlib.h>#include <pthread.h>
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;}

【C语言·Linux】多线程基础

从运行的结果可以看到,已经分别计算出了前后两部分的结果。

如果想要func函数返回数值,好像不是很方便,那就可以重新构造一下结构体。

#include <stdio.h>#include <stdlib.h>#include <pthread.h>
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;}

用这种方式,同样可以返回结果。

如果用全局变量的方式来实现呢?

#include <stdio.h>#include <stdlib.h>#include <pthread.h>
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;}

【C语言·Linux】多线程基础

这个结果对不对?

用全局变量需要注意哪些问题?

#include <stdio.h>#include <stdlib.h>#include <pthread.h>
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;}

【C语言·Linux】多线程基础

运行结果显示,每次运行的结果都不相同,这是为什么,为什么结果不是都等于2000000?按理说,两个线程分别执行1000000次,结果应该是两倍可惜结果不是。


【C语言·Linux】多线程基础

当两个线程在执行的时候,th1完成读操作还没完成写操作的时候,th2可能进行了读操作,那么th2读到的数值,就是s,而当th1完成写操作的时候,值变成了s+1,假设此时th2也进行写操作,同样也是s+1,导致两次加法,第一次加法的结果被第二次的结果覆盖。当执行此时很多的时候,这种差异就变得很明显。

如何处理这种问题?

锁是一种解决方案。

#include <stdio.h>#include <stdlib.h>#include <pthread.h>
pthread_mutex_t lock;//define lock
int 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 lock
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;}

【C语言·Linux】多线程基础

加锁之后运行,就不会发生错误。

看一下运行时间,其实还是比较慢的,这个过程中,不断地上锁解锁,非常耗费时间。锁的位置,直接关系到程序执行的效率。


再写一种实现方式

#include <stdio.h>#include <stdlib.h>#include <pthread.h>
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的内容时,不会一次性复制很多个,当两个值距离比较远的时候,就不会受到影响。