c语言实现多线程并发
首先,c语言的多线程并发,需要用到 pthread.h 库。
#include <pthread.h>
1、开启一个线程
下面代码是最基本的多线程实现:
主要分为三步:
1、声明一个线程变量th,类型为pthread_t;
2、使用pthread_create函数进行创建,第一个参数是线程变量的地址,第三个参数是线程执行的函数(返回值为void*);
3、pthread_join函数等待;
编译的时候要注意,涉及到多线程的时候,得在gcc参数里加上 -lpthread:
可以发现,成功输出了hello world。
2、开启两个线程
当我们开启两个线程,代码如下:
执行的任务都是打印1~499的时候可以发现:
输出的时候,两个线程是错序的。
再对代码做以下修改:
这里我们用到了pthread_create函数的第4个参数,这个参数的传入会反应到myfunc中的形参中去。最后输出的结果,我们可以清晰地看出th1和th2的线程标记和交错运行:
这里th2输出数字58的时候,th1才开始输出数字1。
3、多线程进行协同运算
我们创建一个数组,其中有5000个元素,我们想用两个线程来共同计算这5000个元素的加法和。
从两个线程的函数可以看出,一个线程计算前2500个值的加法和,另一个线程计算后2500个值的加法和。
main函数中,在pthread_join函数等待的th1和th2都结束后,输出对应的值。
编译运行后:
代码改进:
可以看到我们的代码里,th1和th2的执行函数中有大量的相似代码,所以我们最后用一个函数来复用,不难想到,需要通过传参的方式来实现代码复用。这里我们定义了一个结构体,结构体中有循环的起始标记first,终止标记last,区间内加法和result。
从上图可以看出,我们把myfunc1和myfun2整合到了myfunc中去。而在main函数中,我们创建线程的时候传入的参数正是结构体指针:
这样在myfunc函数中,我们就可以对传入的结构体参数中的元素进行利用了,将计算所得传到结构体的result中去。这样我们输出加法和,就可以得到跟上面一样的结果,但是代码会更整洁漂亮:
4、并发程序引起的共享内存问题
有如下代码。有两个进程,两个进程共享全局变量s。两个进程都执行一个计数功能的函数,直观地看过去,th1运行时s++要执行10000次,th2运行时s++也要执行10000次,似乎计算得到的最后s应该是20000。但实际上是这样的吗?
编译运行后,发现输出如下:
s并不是20000,而是12657。
原因其实很简单:
当我们执行s++,底层发生的事件其实是:内存中读取s→将s+1→将s写入到内存。这不是一个原子化操作,当两个线程交错运行的时候,很容易发生结果的丢失。因此最后的结果肯定是要小于20000的。这种情况有种专有名词,叫race condition。
为了解决这个问题,我们可以加锁。
改进后的代码如下,学过操作系统会很好理解,无非就是为了保证共享内存区(临界区)的原子化操作,我们可以在进这段代码之前加锁(pthread_mutex_lock),意味着其他线程看到这段内存被其他人占有的时候,就不去抢占,等这段内存被解锁(pthread_mutex_unlock)之后,它才有读写这段临界区的权利。
但其实这种方式的执行速度并不快,比如这段代码里,每个线程都要进行10000次加解锁的操作,它能解决内存读写冲突的问题,但是却牺牲了效率。