Linux下c语言多线程编程

Linux下c语言多线程编程创建线程函数pthread_create()和等待线程函数pthread_join()的用法。注意:在创建线程pthread_create()之前,要先定义线程标识符:pthread_t自定义线程名;例子1:创建线程以及等待线程执行完毕。#include<stdio.h>#include<stdlib.h>#include<pthread.h>//线程要运行的函数,除了函数名myfunc,其他全都是固定的。void*myfunc(){ p

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

创建线程函数pthread_create()和等待线程函数pthread_join()的用法。
注意:在创建线程pthread_create()之前,要先定义线程标识符:

pthread_t 自定义线程名;

例子1:创建线程以及等待线程执行完毕。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

//线程要运行的函数,除了函数名myfunc,其他全都是固定的。
void* myfunc()
{ 
   
	printf("Hello World!\n");
	return NULL;
}

int main()
{ 
   
	pthread_t th;//在创建线程之前要先定义线程标识符th,相当于int a这样

	pthread_create(&th,NULL,myfunc,NULL);
	/*第一个参数是要创建的线程的地址 第二个参数是要创建的这个线程的属性,一般为NULL 第三个参数是这条线程要运行的函数名 第四个参数三这条线程要运行的函数的参数*/
	
	pthread_join(th,NULL);
	/*线程等待函数,等待子线程都结束之后,整个程序才能结束 第一个参数是子线程标识符,第二个参数是用户定义的指针用来存储线程结束时的返回值*/
	return 0;
}

//编译运行多线程的程序,要在gcc命令尾部加上-lpthread
//gcc example1.c -lpthread -o example1

在这里插入图片描述
例子二:创建两条线程以及等待两条线程执行完毕

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void* myfunc()
{ 
   
	int i;
	for(i=1;i<50;i++)
	{ 
   
		printf("%d\n",i);

	}
	return NULL;
}

int main()
{ 
   
	pthread_t th1;
	pthread_t th2;

	pthread_create(&th1,NULL,myfunc,NULL);
	pthread_create(&th2,NULL,myfunc,NULL);

	pthread_join(th1,NULL);
	pthread_join(th2,NULL);
	return 0;
}

在这里插入图片描述
运行我们可以看到,线程1两条线程的执行方式是怎么样的,
线程1数到46就被挂起了,轮到线程二执行,cpu给线程二一个时间片,线程二在这个时间片内执行只数到20就被挂起了。然后cpu立即切换去执行线程1,线程1继续执行数到49执行完毕立即结束。CPU就立刻去执行剩下的线程二,直到执行结束。
两条线程是同时在随机交叉着运行的。
单核CPU就是这样子随机分配时间片给线程一直交换着执行,这叫并发执行。

如果运行的时候发现它是一条线程运行完了才换另一条,那可能就是cpu给他分配是时间片太多了而已让他直接就执行完毕了,线程运行确实是交换着执行的。
在这里插入图片描述
例子3
我们想看看哪些数字是第一条线程打印出来的,哪些数字是第二条线程打印出来的。
可以通过传递参数的方法来查看。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void* myfunc(void* args)
{ 
   
	int i;
	//由于“th1”是字符串,所以这里我们要做个强制转换,把void*强制转换为char*
	char* name = (char*) args;
	for(i=1;i<50;i++)
	{ 
   
		printf("%s:%d\n",name,i);

	}
	return NULL;
}

int main()
{ 
   
	pthread_t th1;
	pthread_t th2;

	pthread_create(&th1,NULL,myfunc,"th1");//pthread_create的第四个参数是要执行的函数的参数哦!~
	//这里的“th1”就是void* args
	pthread_create(&th2,NULL,myfunc,"th2");

	pthread_join(th1,NULL);
	pthread_join(th2,NULL);
	return 0;
}

运行之后可以看到哪些数字是th1打印的,哪些数字是th2打印的。
例子4
定义一个大小为5000的数组,随机生成5000个数,我们想创建两条线程,让这两条线程去计算这5000个数字的和,第一条线程计算前2500个数的和,第二条线程让它算后2500个数字的和。怎么做?

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int arr[5000];
int s1=0;
int s2=0;
void* myfunc1(void* args)
{ 
   
	int i;
	
	for(i=0;i<2500;i++)
	{ 
   
		s1 = s1 + arr[i];

	}
	return NULL;
}
void* myfunc2(void* args)
{ 
   
        int i;
 
        for(i=2500;i<5000;i++)
        { 
   
                s2 = s2 + arr[i];
 
        }
        return NULL;
 }


int main()
{ 
   
	//初始化数组
	int i;
	for(i=0;i<5000;i++)
	{ 
   
		arr[i] = rand() % 50;

	}
	
	/* for(i=0;i<5000;i++) { printf("a[%d]=%d\n",i,arr[i]); }*/
	pthread_t th1;
	pthread_t th2;

	pthread_create(&th1,NULL,myfunc1,NULL);
	pthread_create(&th2,NULL,myfunc2,NULL);

	pthread_join(th1,NULL);
	pthread_join(th2,NULL);
	
	printf("s1=%d\n",s1);
	printf("s2=%d\n",s2);
	printf("s1+s2=%d\n",s1+s2);
	return 0;
}

在这里插入图片描述
例子5
上一个例子的代码重复率太高,我们对其优化,加入了结构体,也只用了同一个函数。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int arr[5000];
int s = 0;
typedef struct { 
   
	int first;
	int last;
}MY_ARGS;

void* myfunc(void* args)
{ 
   
	int i;
	MY_ARGS* my_args = (MY_ARGS*)args;
	int first = my_args->first;
	int last = my_args->last;
	for(i=first;i<last;i++)
	{ 
   
		s = s + arr[i];

	}
	printf("s = %d\n",s);
	s=0;
	return NULL;
}



int main()
{ 
   
	//初始化数组
	int i;
	for(i=0;i<5000;i++)
	{ 
   
		arr[i] = rand() % 50;

	}
	
	/* for(i=0;i<5000;i++) { printf("a[%d]=%d\n",i,arr[i]); }*/
	pthread_t th1;
	pthread_t th2;
	//设置两个结构体变量作为参数
	MY_ARGS args1 = { 
   0,2500};
	MY_ARGS args2 = { 
   2500,5000};

	pthread_create(&th1,NULL,myfunc,&args1);
	pthread_create(&th2,NULL,myfunc,&args2);

	pthread_join(th1,NULL);
	pthread_join(th2,NULL);
	
	return 0;
}

例子6
来看看如果把s加在全局变量,让s++循环10000次后会发生什么?

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int s = 0;
void* myfunc(void* args)
{ 
   
	int i = 0;
	for(i=0;i<1000000;i++)
	{ 
   
		s++;
	}
	return NULL;
}

int main()
{ 
   
	pthread_t th1;
	pthread_t th2;
	
	pthread_create(&th1,NULL,myfunc,NULL);
	pthread_create(&th2,NULL,myfunc,NULL);
	
	pthread_join(th1,NULL);
	pthread_join(th2,NULL);

	printf("s = %d\n",s);
	return 0;
}

在这里插入图片描述
我们发现每次执行后的s都不一样,按理说s应该是200000才对呀,为什么会这样呢?

因为在第一条线程读s并s++的时候,第二条线程也会来读,可能在第一条线程进行加之前读也可能在加之后读,所以我们会丢失一些s++,所以每次运行出来的结果都不一样。
这种情况叫做race condition,当出现race contion的时候,就很有可能会出现错误的结果。

那么要如何解决race condition呢?
最常用的方法就是加锁。
有一种锁叫mutex
我们看看mutex要怎么用?

pthread_mutex_t lock;

这种pthread_mutex_t的数据类型叫锁
定义一个锁后要对锁进行初始化
pthread_mutex_init(&lock,NULL);
锁初始化函数有两个参数,第一个参数就是我们定义的锁,第二个参数是互斥锁的属性,写NULL就可以了,代表默认的快速互斥锁。

参考互斥锁创建:https://blog.csdn.net/fanyun_01/article/details/107648187?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164863226016782089367009%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164863226016782089367009&biz_id=0&spm=1018.2226.3001.4187

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_mutex_t lock;//定义一个互斥锁
int s = 0;


void* myfunc(void* args)
{ 
   
	int i = 0;
	pthread_mutex_lock(&lock);//这个函数表示,在这个地方上一个锁,就是摆一个锁在这个地方

	for(i=0;i<100000;i++)
	{ 
   
		s++;
	}
	pthread_mutex_unlock(&lock);//把这个锁给解掉

	return NULL;
}

int main()
{ 
   
	pthread_t th1;
	pthread_t th2;

	pthread_mutex_init(&lock,NULL);//初始化这个锁,此时只是创建了这个锁而已,还没有加进去哦。
	/*锁不是用来锁一个变量,它是用来锁住一段代码的。*/
	
	pthread_create(&th1,NULL,myfunc,NULL);
	pthread_create(&th2,NULL,myfunc,NULL);
	
	pthread_join(th1,NULL);
	pthread_join(th2,NULL);

	printf("s = %d\n",s);
	return 0;
}

在这里插入图片描述
解释一下上图的结果,加了锁之后得到的结果就是正确的了,第一次运行我是把锁加在for循环里头,可以看到运行的时间是.0.01ms是很慢的,而第二次运行也就是把锁加在for循环的外头,可以看到速度就快多了,所以加锁的位置很重要,最好不要加在循环里面,不然要一直循环开锁解锁就特别慢。
讲一下两条线程是遇到这个加锁的代码是怎么做的,
两条线程看谁先抢到这个锁,也是竞争在抢锁,如果是th1先抢到,那锁就是th1的了,拿到锁的线程就很自私,接下来锁里面的代码就是th1自己一个人的,th2就不能来读这段代码了,th2没抢到锁的话它自己是不会去自己加个锁的,th2只能靠边站了,等th1先走完了锁里的代码,然后解锁了,再轮th2,加锁可以保证两条线程不会去抢着读数据,导致结果出错。
加了锁,多线程就变成了两个单线程按顺序串行着走完,两个for循环是独立存在的。
互斥锁的作用:https://blog.csdn.net/galaxyxupt/article/details/81613181?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164868737616780261991331%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164868737616780261991331&biz_id=0&spm=1018.2226.3001.4187
https://blog.csdn.net/qq_39736982/article/details/82348672?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164868779716781685333883%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164868779716781685333883&biz_id=0&spm=1018.2226.3001.4187
互斥锁可以 防止两条线程竞争共享数据资源而引起的与时间上有关的数据混乱。
每个线程在对共享资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。
但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了。

在访问共享资源前加锁,访问结束后立即解锁。锁的“粒度”应越小越好。
https://blog.csdn.net/qq_34827674/article/details/108608566?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164868779716781685333883%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164868779716781685333883&biz_id=0&spm=1018.2226.3001.4187
在这里插入图片描述
多核的假共享的概念False sharing

为了避免假共享,最好分别把记录的结果当成局部变量。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/179624.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • JavaWeb专栏之(四):什么是servlet(底层源码及执行流分析)?[通俗易懂]

    JavaWeb专栏之(四):什么是servlet(底层源码及执行流分析)?[通俗易懂]JavaWeb专栏之(四):什么是servlet(底层源码及执行流分析)?前言:本文探究JavaWeb中Servlet的源码及执行流过程,相信您看完后,为金三银四的面试中如虎添翼。俗话知其然,知其所以然。底层代码的理解擦才是YYDS,让小Du猿带您一起走进Servlet的底层实现源码吧</p>最后:本专栏的代码已经同步到Gitee中,欢迎小伙伴一键start,原创整理不易,多多支持哦!传送门:https://gitee.com/shunchangdu/Javaweb_se

  • 第七次人口普查数据可视化分析实战——基于pyecharts(含数据和源码)[通俗易懂]

    第七次人口普查数据可视化分析实战——基于pyecharts(含数据和源码)[通俗易懂]第七次人口普查数据分析实战?个人主页:JoJo的数据分析历险记?个人介绍:小编大四统计在读,目前保研到统计学top3高校继续攻读统计研究生?如果文章对你有帮助,欢迎✌关注、?点赞、✌收藏、?订阅专栏国家统计局发布的第七次人口普查数据较为宏观,未能较好的体现各地区人口指标的分布情况。本文基于Pyecharts对各地区人口普查数据的分布情况进行可视化分析?。

  • pycharm全家桶激活码2021年_通用破解码[通俗易懂]

    pycharm全家桶激活码2021年_通用破解码,https://javaforall.cn/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

  • python聊天室(tkinter写界面,treading,socket实现私聊群聊查看聊天记录,mysql存储数据)

    python聊天室(tkinter写界面,treading,socket实现私聊群聊查看聊天记录,mysql存储数据)一、前言我用的是面向对象写的,把界面功能模块封装成类,然后在客户端创建对象然后进行调用。好处就是方便我们维护代码以及把相应的信息封装起来,每一个实例都是各不相同的。所有的界面按钮处理事件都在客户端,在创建界面对象是会把客户端的处理事件函数作为创建对象的参数,之后再按钮上绑定这个函数,当点击按钮时便会回调函数二、登录界面实现登录界面模块chat_login_panel.pyfromtkinterimport*#导入模块,用户创建GUI界面#登陆界面类classLoginPane

    2022年10月27日
  • STM32开发项目:ADS1115的驱动与使用

    STM32开发项目:ADS1115的驱动与使用日期作者版本说明2020.09.24TaoV0.0完成主体内容的撰写目录ADS1115介绍驱动源码头文件源文件使用指南基本步骤注意事项ADS1115介绍ADS1115是具有PGA、振荡器、电压基准、比较器的16位、860SPS、4通道Δ-ΣADC,数据通过一个I2C兼容型串行接口进行传输。有关它的详细说明可以参考官方数据手册。驱动源码头文件#ifndef__ADS1115_H__#define__ADS1115_H__#include…

    2022年10月29日
  • goland刷新时间永久激活破解方法

    goland刷新时间永久激活破解方法,https://javaforall.cn/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号