C语言多线程编程基础

这篇文章是我看了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)中参数的调用,限于篇幅原因,这里就不说了,但是文章开头给的视频中由详细的解释。想要了解的同学们可以观看这条视频。
删除线格式
溜了溜了~~

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值