目录
学习资料
最近在学习一下多线程编程,学习了多线程的一些入门知识,记录下来
网站URL: https://www.bilibili.com/video/BV1kt411z7ND/
网站标题: [C语言]多线程程序入门教程
线程和进程
两个都可以并行计算,但是线程是可以共享内存的,但是进程是不能共享能存的。
例如不同线程进行a++
,则加的是同一个a
但是不同的进程进行a++
,则是不同的a
我们使用线程并行计算的时候,我们可以通过把大任务分成小任务来同时计算,比如计算1-5000的累加和,我们可以用一个线程计算1-2500,另一个线程计算2501-5000,最终把两个数字相加来实现这个总任务。
pthread_create
#include <stdio.h>
#include <stdlib.h>
#include <phread.h>
void* myfunc(void* args)
{
printf("hello world");
return NULL;
}
int main(){
pthread_t th;
pthread_create(&th, NULL,myfunc, NULL);
}
在多线程编程中,我们需要使用pthread_create进行线程的创建。第一个参数是线程的名字,第二个参数一般不需要,第三个参数是我们要在此线程中运行的函数,最后一个是传递到线程中的参数。
注意!如果代码中是多线程编程的话,编译的时候需要加入-lpthread
若线程还没结束,而主线程已经结束的话,会导致主线程直接将线程关闭,不再执行
pthread_join
词函数可以等待线程运行结束之后再退出主线程
//单挑线程等待
pthread_join(th, NULL);
//多条线程等待
pthread_join(th2, NULL);
pthread_join(th2, NULL);
传递参数
我们将前面的pthread_start
改一下参数,改为pthread_create(&th, NULL,myfunc, "th1");
这样,就会把参数"th1"传递给void* myfunc(void* args)
中的无类型变量args
而这个时候我们需要怎么来应用这传递的变量呢?我们只需要来一个强制的类型转换就OK。
这里就涉及到一个应用场景,利用void进行对未知类型变量的接收。
void,其实就是指向了一块内存地址,无论你是什么变量进行传递,你都可以用void*来进行一个指向,在之后,我们再根据变量进行一个强制类型转换就可以实现参数传递,因为内存并没有变,我们只是更改了解释的方式而已。
如何使用
既然如此,那么void*
有什么用呢?实际上我们在很多接口中都会发现它们的参数类型都是void*
, 例如:
ssize_t read(int fd, void *buf, size_t count);
void *memcpy(void *dest, const void *src, size_t n);
为何要如此设计?因为对于这种通用型接口,你不知道用户的数据类型是什么,但是你必须能够处理用户的各种类型数据,因而会使用
void*
。void*
能包容地接受各种类型的指针。也就是说,如果你期望接口能够接受任何类型的参数,你可以使用void*
类型。但是在具体使用的时候,你必须转换为具体的指针类型。例如,你传入接口的是int*
,那么你在使用的时候就应该按照int*
使用。
注意
使用void*
需要特别注意的是,你必须清楚原始传入的是什么类型,然后转换成对应类型。
知乎——void*是怎样的存在
传参实例
#include <stdio.h>
#include <stdlib.h>
#include <phread.h>
void* myfunc(void* args)
{
int i;
char* name = (char*) args;
for(i=1; i<50; i++){
printf("%s":"%d\n", name, i);
}
return NULL;
}
int main(){
pthread_t th1,th2;
pthread_create(&th1, NULL,myfunc, "th1");
pthread_create(&th2, NULL,myfunc, "th2");
}
这样就实现了两条线程输出数字,并且实现了字符变量的传递。
多个参数的传递
我们如果需要传递多个参数的时候,我们可以将多个参数整合到一个结构体里面,然后将其强制转化为该结构体变量即可。这种方法还可以节省我们定义的函数,用同样的函数根据不同的变量实现不同的效果。
例如:MY_ARGS args = (MY_ARGS*) args
参数的返回
依据上一条的原理,我们其实同样可以在结构体中定义一个变量result
,在该线程的末尾,将结果传递过去,从而实现了一种类似函数参数返回的效果
race condition情况
注意,不同线程不要对一个全局变量进行一个操作,理由如下:
这个时候就会出现race condition的情况
比如,两个线程,同时对一个全局变量s
循环进行s++
的操作
那么两个线程进行的操作如下:
读取s变量内存
--> 对读取的值进行++操作
--> 将++之后的值写回内存
这样就可能会出现一种情况,当其中一个线程使用了读取完s变量但还没写回内存的时候,另一个线程也对此内存进行了读取,并进行操作,这样两个线程就出现了竞争关系,导致了最终的结果不准确的情况。
解决方法——加锁:mutex
可以定义一个这个变量类型的pthread_mutex_t lock
使用之前我们需要先初始化这个锁 pthread_mutex_init(&lock, NULL)
,第二个变量可以先用NULL代替
加锁pthread_mutex_lock(&lock)
解锁pthread_mutex_unlock(&lock)
只有这一个进程拿到锁,锁上了才能够继续往下运行下去,另外一个进程就只能等待这一个进程解锁才能够拿到锁进行运行。
但是如果我们程序里面的加锁解锁太过于频繁会导致程序运行的实际时间太长。
False Sharing
运算的时候,如果多个线程的核使用的变量之间的间隔非常靠近,则会出现一个假共享的情况,其具体情况就是CPU为了计算快速,一般会将需要用到的变量的相邻路径的变量放到缓存中,这样如果两个线程用两个相近的变量就会都读到自己的内核中,处理之后再放回去,而放回去的时候会出现相邻变量有区别,导致读写速度的浪费,也就导致了时间的浪费。