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)


相关推荐

  • 用几何光学方法如何分析反射调制方式光纤位移_相位单点激光测距

    用几何光学方法如何分析反射调制方式光纤位移_相位单点激光测距本文介绍了移相干涉技术中最基础却也非常重要的一步——相位提取,主要阐述了移相干涉测量原理、四步移相法提取相位、多步平均法推导过程、多步解包裹后平均法这四个部分,希望能给同样从事该领域研究的你带来一点帮助。

  • android studio接口调用_android studio jdk版本

    android studio接口调用_android studio jdk版本Android做jni的时候需要根据nativejava类生成对应的.h头文件,然后根据.h头文件写cpp文件。在Androidstudio中可以添加自定义工具,将javah指令添加进去首先我们看下javah的指令格式由此指令我们知道怎么使用javah指令例如有java文件D:\project\Test\app\src\main\java\com\example\test.java编译生成的class文件位于D:\project\Test\app\build\interm.

  • sizeof和strlen的区别(strlen和sizeof的用法)

    charstr[20]=”0123456789″;int  a=strlen(str);/*a=10;strlen计算字符串的长度,以�为字符串结束标记。int  b=sizeof(str);/*b=20;sizeof计算的则是分配的数组str[20]所占的内存空间的大小,不受里面存储的内容影响========================================

  • java集合源码分析(二):List与AbstractList

    java集合源码分析(二):List与AbstractList概述List应该接口是Collection最常被使用的接口了。其下的实现类皆为有序列表,其中主要分为Vector,ArrayList,LinkedList三个实现类,其中Vecotr又

  • android scaleanimation动画,Android 的ScaleAnimation 缩放动画基本运用

    android scaleanimation动画,Android 的ScaleAnimation 缩放动画基本运用因为今天用到了ScaleAnimation缩放动画就写一下,加深一下印象。用ScaleAnimation有几个重载方法,这里就将八个参数的重载方法。ScaleAnimation(floatfromX,floattoX,floatfromY,floattoY,intpivotXType,floatpivotXValue,intpivotYType,floatpivotYV…

    2022年10月16日
  • linux 同步IO: sync、fsync与fdatasync

    linux 同步IO: sync、fsync与fdatasync传统的UNIX实现在内核中设有缓冲区高速缓存或页面高速缓存,大多数磁盘I/O都通过缓冲进行。当将数据写入文件时,内核通常先将该数据复制到其中一个缓冲区中,如果该缓冲区尚未写满,则并不将其排入输出队列,而是等待其写满或者当内核需要重用该缓冲区以便存放其他磁盘块数据时,再将该缓冲排入输出队列,然后待其到达队首时,才进行实际的I/O操作。这种输出方式被称为延迟写(delayedwrite)(Bach

发表回复

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

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