这篇文章是我看了b站一个up主 正月点灯笼 的多线程教学视频之后写的一篇学习总结,供大家参考指正。
视频链接在此:https://www.bilibili.com/video/BV1kt411z7ND?p=3
通常我们写的基础C语言程序都是只有一条线程,但是因为我们的计算机cpu不只有一个内核,所以如果我们可以写出来多线程的程序,就可以让cpu的几个核同时工作运算,可以比单线程程序更好的发挥cpu的功能,让我们一起来看一下怎么写出基础的多线程程序。
首先,在头文件中我们要声明#include<pthread.h>,用于调用多线程的相关函数,然后我们定义现成的句柄q
pthread_t th1;
这行代码就像普通的 int a;一样,就是声明一个变量,并没有给它赋值,如果我们想要使用这个句柄,我们就要创建一条进程
pthread_create(&th1,NULL,myfunc,NULL);
第一个参数为指向线程标识符的指针。
第二个参数用来设置线程属性。
第三个参数是线程运行函数的起始地址。
最后一个参数是运行函数的参数。
我们需要像创建线程的函数中传入句柄的地址 &th1,
线程的属性默认为NULL
myfunc是要在这条线程中执行的函数名
NULL表明不向函数中传入任何参数
如果我们想要用一条线程来执行一个最简单的hello world函数,我们可以这样写
#include<stdio.h>
#include<pthread.h>
void* myfunc(void *args)//函数的返回值和参数必须这样写
{
printf("hello world\n");
return NULL;
}
int main()
{
pthread_t th;
pthread_create(&th,NULL,myfunc,NULL);
pthread_join(th,NULL);
return 0;
}
大家会注意到里面有一行代码之前没有提到,就是pthread_join(th,NULL)
这里做一说明
函数pthread_join用来等待一个线程的结束。头文件 : #include <pthread.h>
函数定义: int pthread_join(pthread_t thread, void **retval);
描述 :pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。
参数 :thread: 线程标识符,即线程ID,标识唯一线程。retval: 用户定义的指针,用来存储被等待线程的返回值。
返回值 : 0代表成功。 失败,返回的则是错误号。
如果去掉这行代码,程序没有任何结果,原因就是在运行线程之前,main函数所在的主进程先到达了结尾,没有给线程运行的时间。
如果有两个线程并行,他们就会互相争夺资源,比如下面这个程序:
#include<stdio.h>
#include<pthread.h>
void* myfunc1(void* args)
{
for(int i = 0;i < 500;i++)
printf("th1:%d\n",i);
return NULL;
}
void* myfunc2(void* args)
{
for(int i = 0;i < 500;i++)
printf("th2:%d\n",i);
return NULL;
}
int main()
{
pthread_t th1,th2;
pthread_create(&th1,NULL,myfunc1,NULL);
pthread_create(&th2,NULL,myfunc2,NULL);
pthread_join(th1,NULL);
pthread_join(th2,NULL);
return 0;
}
我们发现运行结果中th1和th2是交替出现的,这就说明了两条进程是在互相争夺资源,互相竞争的,一旦他们是互相竞争的,那么如果两条进程互相合作完成一个任务时,就要为他们指定相应的规则,不然就会出现这种情况:
#include<stdio.h>
#include<pthread.h>
int s = 0;
void* myfunc1(void* args)
{
for(int i = 0;i < 100000;i++)
s++;
return NULL;
}
void* myfunc2(void* args)
{
for(int i = 0;i < 100000;i++)
s++;
return NULL;
}
int main()
{
pthread_t th1,th2;
pthread_create(&th1,NULL,myfunc1,NULL);
pthread_create(&th2,NULL,myfunc2,NULL);
pthread_join(th1,NULL);
pthread_join(th2,NULL);
printf("%d\n",s);
return 0;
}
这个程序用两条线程对全局变量s进行累加,一旦数据量达到100000这个比较大的级别,两条线程之间合作就会出现问题,相加结果不为200000。
原因是这样的:
现在s的值是1,th1将s的值记录下来,在th1中对s累加,
这之间是需要时间的,
如果在th1将s的值记录之后,在th1将s的值改变之前,th2将s的值记录了下来,
那么th2中s的值就是1,并不是s应该的值2,
这样th1和th2分别对s进行累加之后,s的值为2,就浪费了一次累加,
这就导致了计算结果不是应该的结果。
那么如何消除这个问题呢?
pthread.h 为我们提供了一种工具叫做“锁”,它可以将线程上锁,在一条线程执行的时候,另一条线程就被锁住了,不能执行,直到被解锁才能执行,如果这样,就不会出现计算错误的情况了。
#include<stdio.h>
#include<pthread.h>
int s = 0;
pthread_mutex_t lock;//声明锁
void* myfunc1(void* args)
{
for(int i = 0;i < 100000;i++)
{
pthread_mutex_lock(&lock);//上锁
s++;
pthread_mutex_unlock(&lock);//解锁
}
return NULL;
}
int main()
{
pthread_t th1,th2;
pthread_mutex_init(&lock,NULL);//将锁初始化
pthread_create(&th1,NULL,myfunc1,NULL);
pthread_create(&th2,NULL,myfunc1,NULL);
pthread_join(th1,NULL);
pthread_join(th2,NULL);
printf("%d\n",s);
return 0;
}
运行这个程序,就可以得到正确的答案,但是我们发现这个程序运行的速度很慢,这是因为将开锁解锁的代码写到了循环里,每次递增的时候都要开锁和解锁,这就消耗了很多的时间,
如果我们把开锁和解锁写在循环的外面,程序运行就会快很多。
#include<stdio.h>
#include<pthread.h>
int s = 0;
pthread_mutex_t lock;//声明锁
void* myfunc1(void* args)
{
pthread_mutex_lock(&lock);
for(int i = 0;i < 100000;i++)
{
s++;
}
pthread_mutex_unlock(&lock);
return NULL;
}
int main()
{
pthread_t th1,th2;
pthread_mutex_init(&lock,NULL);
pthread_create(&th1,NULL,myfunc1,NULL);
pthread_create(&th2,NULL,myfunc1,NULL);
pthread_join(th1,NULL);
pthread_join(th2,NULL);
printf("%d\n",s);
return 0;
}
这次程序运行的时间减少了不少。
关于void* myfunc(void* args)中参数的调用,限于篇幅原因,这里就不说了,但是文章开头给的视频中由详细的解释。想要了解的同学们可以观看这条视频。
删除线格式
溜了溜了~~