【C语言】多线程基本实现

这篇具有很好参考价值的文章主要介绍了【C语言】多线程基本实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


在串口助手编程中,-k命令下需要实现等待接收message的同时可以发送键入message。但是,键入message使用的fgets()函数如果得不到键入就会一直等待,无法继续接收message,考虑采用多线程实现有键入则发送,否则一直等待接收message。

基础知识

  • 计算机的核心是CPU,承担所有的计算任务。
  • 操作系统是计算机的管理者,负责任务的调度、资源的分配和管理,管理整个计算机硬件。
  • 应用程序是具有某种功能的程序,运行于操作系统上。

进程

进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是程序的一次执行过程,是临时的、有生命期的、动态产生动态消亡的。进程是一种抽象的噶爱念,没有统一的标准定义。

  • 进程是一个可拥有资源的独立单位;
  • 进程是一个可以独立调度和分派的基本单位。

进程由程序、数据集合和进程控制块三部分组成:

  • 程序:描述进程要完成的功能,是控制进程执行的指令集;
  • 数据集合:程序执行时所需要的数据和工作区;
  • 程序控制块(PCB):包含进程的描述信息和控制信息,是进程存在的唯一标志。

线程

线程:进程中的一个实体,是CPU调度和分派的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器、一组寄存器和栈),但它可以与同属一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行。线程在运行中呈现间断性。

线程建立之初,就是为了将进程的上述两个属性分开,线程构成了“CPU调度和分派的基本单位”,这样一个进程中可以有很多线程,操作系统对线程进行调度和分派,可以更好地实现进程的并打执行;同时同一个进程下的线程可以共享该进程的全部资源,可以满足同一个进程下不同线程对进程资源的访问。线程的出现,巧妙地将进程的两个属性分开,使得进程可以更好地处理并行执行的需求。

线程在Unix系统下,通常被称为轻量级的进程,线程虽然不是进程,但却可以看作是Unix进程的表亲,同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。

  • 是程序执行中的一个单一的顺序控制流程
  • 是程序执行流的最小单元
  • 是处理器调度和分派的基本单位

一个进程可以一个或多个线程,各个线程共享程序的内存空间,即所在进程的内存空间(不包括栈),每条线程并行执行不同的任务。一个标准的线程由线程ID、当前指令助阵(PC)、寄存器和堆栈组成。进程由内存空间(代码、数据、进程空间、打开的文件)和一个或多个线程组成。

进程是系统进行资源分配和调度的一个独立单位,线程是进程的一个实体,是CPU调度和分派的基本单位,线程只是一段程序的执行。

比如打开了一个软件,能从任务管理器,就能看到有该软件这样一个进程,这时候想跟别人聊个天,打开对话框,这就是运行一个线程;查看一下聊天的这个人的资料,这又运行了另外一个线程。

同一个进程下的线程是资源共享的,进程与进程之间都是独立的。

线程的优点

线程可以提高应用程序在多核环境下处理诸如文件I/O或者socket I/O等会产生堵塞的情况的表现性能。在Unix系统中,一个进程包含很多东西,包括可执行程序以及一大堆的诸如文件描述符地址空间等资源。在很多情况下,完成相关任务的不同代码间需要交换数据。如果采用多进程的方式,那么通信就需要在用户空间和内核空间进行频繁的切换,开销很大。但是如果使用多线程的方式,因为可以使用共享的全局变量,所以线程间的通信(数据交换)变得非常高效。

  • 避免拥塞
    单个线程中的程序,是按照顺序执行的,排在前面的程序如果发生异常卡住(阻塞),会影响到后面的程序执行。多线程就等于是异步调用,避免这个情况。

  • 避免CPU的空转
    这个比如一个网页,如果是单线程的话,服务器处理一条请求后,会等待下一个请求,这时候CPU处于一个闲置的状态。多线程能避免这个问题。

  • 提升效率

C语言多线程

多线程是多任务处理的一种特殊形式,多任务处理允许让电脑同时运行两个或两个以上的程序。一般情况下,两种类型的多任务处理:基于进程和基于线程。

基于进程的多任务处理是程序的并发执行。
基于线程的多任务处理是同一程序的片段的并发执行。
多线程程序包含可以同时运行的两个或多个部分。这样的程序中的每个部分称为一个线程,每个线程定义了一个单独的执行路径。

本教程假设您使用的是 Linux 操作系统,我们要使用 POSIX 编写多线程 C++ 程序。POSIX Threads 或 Pthreads 提供的 API 可在多种类 Unix POSIX 系统上可用,比如 FreeBSD、NetBSD、GNU/Linux、Mac OS X 和 Solaris。

创建线程

首先,使用pthread_create函数创建一个线程。该函数定义在头文件pthread.h 中,函数原型为:

int pthread_create(

    pthread_t *restrict tidp,

    const pthread_attr_t *restrict attr,

    void *(*start_rtn)(void *),

    void *restrict arg

  );

线程创建函数pthread_create包含四个变量,分别为:

  1. 一个线程变量名,被创建线程的标识,存储线程ID,线程的句柄,可通过该变量操纵指向的线程
  2. 线程的属性指针,缺省为NULL即可
  3. 被创建线程执行的函数程序代码
  4. 参数3中函数程序代码的传入参数,不需要则为NULL

返回值:成功返回0,失败返回错误编号。

创建一个POSIX 线程实例:

pthread_create (thread, attr, start_routine, arg)
参数 描述
thread 指向线程标识符指针。
attr 一个不透明的属性对象,可以被用来设置线程属性。您可以指定线程属性对象,也可以使用默认值 NULL。
start_routine 线程运行函数起始地址,一旦线程被创建就会执行。
arg 运行函数的参数。它必须通过把引用作为指针强制转换为 void 类型进行传递。如果没有传递参数,则使用 NULL。

终止线程

pthread_exit(void *retval); //retval用于存放线程结束的退出状态

终止一个 POSIX 线程:

#include <pthread.h>
pthread_exit (status)

pthread_exit 用于显式地退出一个线程。通常情况下,pthread_exit() 函数是在线程完成工作后无需继续存在时被调用。

如果 main() 是在它所创建的线程之前结束,并通过 pthread_exit() 退出,那么其他线程将继续执行。否则,它们将在 main() 结束时自动被终止。

连接线程

pthread_create调用成功以后,新线程和老线程谁先执行,谁后执行用户是不知道的,这一块取决与操作系统对线程的调度,如果我们需要等待指定线程结束,需要使用pthread_join函数,这个函数实际上类似与多进程编程中的waitpid。
pthread_join()函数的功能是等待一个线程的结束,它是一个线程阻塞的函数。

pthread_join有两个参数:指定要等待的线程id;接收线程函数的返回值。
函数原型为:

int pthread_join(pthread_t thread,void**retval);

等待第一个参数的线程执行完成后,去执行retval指向的函数(起到线程同步的作用)。th是要等待结束的线程的标识;指针thread_return指向的位置存放的是终止线程的返回状态。例如假设 A 线程调用 pthread_join 试图去操作B线程,该函数将A线程阻塞,直到B线程退出,当B线程退出以后,A线程会收集B线程的返回码。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NUMBER_OF_THREADS 10
 
void* ptintf_hello_world(void* tid);
int main(void){
    pthread_t threads[NUMBER_OF_THREADS];
    int status,i;
    for(i=0;i<NUMBER_OF_THREADS;i++){//循环创建10个现场
    	printf("Main here. Creating thread %d\n",i);
        //创建线程,线程函数传入参数为i
    	status=pthread_create(&threads[i],NULL,ptintf_hello_world,(void*)i);
		if(status!=0){//线程创建不成功,打印错误信息
    		printf("pthread_create returned error code %d\n",status);
    		exit(-1);
		}
	}
	exit(0);
}
void* ptintf_hello_world(void* tid){
	printf("Hello world %d.\n",tid);//在线程函数中打印函数的参数
	pthread_exit(0);
}

由于我们没有在主线程中等待我们创建出来的10个线程执行完毕,所以创建出来的子线程可能还没来得及执行,就因为主线程(main函数)执行完毕而终止整个进程,导致子线程没法运行。因此printf得到的“Hello world ”不是10个,其数量是无法预知的,其顺序也是无法预知的。此时我们就需要pthread_join()函数来等待线程执行完成。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NUMBER_OF_THREADS 10
 
void* ptintf_hello_world(void* tid);
int main(void){
    pthread_t threads[NUMBER_OF_THREADS];
    int status,i;
    for(i=0;i<NUMBER_OF_THREADS;i++){//循环创建10个现场
    	printf("Main here. Creating thread %d\n",i);
        //创建线程,线程函数传入参数为i
    	status=pthread_create(&threads[i],NULL,ptintf_hello_world,(void*)i);
		if(status!=0){//线程创建不成功,打印错误信息
    		printf("pthread_create returned error code %d\n",status);
    		exit(-1);
		}
	}
for(i=0;i<NUMBER_OF_THREADS;i++){
		pthread_join(threads[i],NULL);
	}
	exit(0);
}
void* ptintf_hello_world(void* tid){
	printf("Hello world %d.\n",tid);//在线程函数中打印函数的参数
	pthread_exit(0);
}

此时所有子线程都执行完毕,打印了对应的“Hello world ”,但是线程执行的顺序是不固定的,也就是说,我们无法预知打印的顺序。根据代码判断程序的输出就是不可行的,我们只知道输出的内容,但是不知道输出的顺序。

除非我们在每个子线程创建之后,一直等待其运行结束,然后才开始创建下一个子线程。即,将pthread_join()函数放到紧挨着pthread_create()函数的后面

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NUMBER_OF_THREADS 10
 
void* ptintf_hello_world(void* tid);
int main(void){
    pthread_t threads[NUMBER_OF_THREADS];
    int status,i;
    for(i=0;i<NUMBER_OF_THREADS;i++){//循环创建10个现场
    	printf("Main here. Creating thread %d\n",i);
        //创建线程,线程函数传入参数为i
    	status=pthread_create(&threads[i],NULL,ptintf_hello_world,(void*)i);
        pthread_join(threads[i],NULL);
		if(status!=0){//线程创建不成功,打印错误信息
    		printf("pthread_create returned error code %d\n",status);
    		exit(-1);
		}
	}
	exit(0);
}
void* ptintf_hello_world(void* tid){
	printf("Hello world %d.\n",tid);//在线程函数中打印函数的参数
	pthread_exit(0);
}

此时,我们实际通过pthread_join()函数将多线程的并行强制为顺序执行.此时打印的输出是有序的,因为pthread_join()函数将等待对应的线程结束,线程资源被收回,才继续执行下面的命令,创建另一个线程。实际相当于并行变为了串行。

除此之外,使用pthread_join()函数还可以利用其第二个参数,得到线程函数的返回值。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NUMBER_OF_THREADS 10
 
void* ptintf_hello_world(void* tid);
int main(void){
    pthread_t threads[NUMBER_OF_THREADS];
    int status,i;
    for(i=0;i<NUMBER_OF_THREADS;i++){
    	printf("Main here. Creating thread %d\n",i);
    	status=pthread_create(&threads[i],NULL,ptintf_hello_world,(void*)i);
		//使用res得到线程函数的返回值 
        int** res=(int**)malloc(sizeof(int*));
		pthread_join(threads[i],(void**)res);  //pthread_join函数以阻塞的方式等待指定的线程结束;如果线程已经结束,函数会立即返回,并且指定的线程必须是joinable的 
    	printf("res[%d]:%d\n",i,**res);//打印线程函数的返回值 
		free(*res); //释放线程处理函数中使用malloc分配的内存空间 
		if(status!=0){
    		printf("pthread_create returned error code %d\n",status);
    		exit(-1);
		}
	}
 
	exit(0);
}
void* ptintf_hello_world(void* tid){
	printf("Hello world %d.\n",tid);
	int* a=(int*)malloc(sizeof(int));
	*a=(int)tid*(int)tid;
	return a;	//线程函数的返回值 
}

新建一个int**类型的变量res,用来接收线程处理函数的返回值,使用pthread_join()函数得到返回值之后,printf打印出来。与此同时,在线程处理函数中,我们将函数的传入参数tid进行平方运算,运算结果作为线程处理函数的返回值。

分离线程

pthread_join (threadid, status) 
pthread_detach (threadid)

pthread_join() 子程序阻碍调用程序,直到指定的 threadid 线程终止为止。当创建一个线程时,它的某个属性会定义它是否是可连接的(joinable)或可分离的(detached)。只有创建时定义为可连接的线程才可以被连接。如果线程创建时被定义为可分离的,则它永远也不能被连。pthread_join() 函数来等待线程的完成。

开启一个线程

最基本的多线程实现

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

void* func(void *args){
	printf("hello\n");
	return NULL;
}

int main(){
	pthread_t th;
	pthread_create(&th, NULL, func, NULL);
	pthread_join(th, NULL);
	return 0;
}

主要分为三步:

  1. 声明一个线程变量th,类型为pthread_t
  2. 使用pthread_create函数创建,第一个参数是线程变量的地址,第三个参数是线程执行的函数;
  3. pthread_join函数等待;

注意:在编译时,pthread_create函数会报未定义引用的错误
原因:pthread库不是Linux系统默认的库,而是POSIX线程库,连接时需要使用库libpthread.a, 在使用pthread_create创建线程时,在编译中要加-lpthread参数。在Linux中将其作为一个库来使用,加上-lpthread以显式链接该库。在Ubuntu Linux命令行中编译时输入:gcc xxx.c -lpthread -o xxx.o

开启两个线程

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

void* func(void *args){
	int i;
	for(i=1; i<500; i++){
		printf("%d\n", i);
	}
	return NULL;
}

int main(){
	pthread_t th1;
	pthread_t th2;
	pthread_create(&th1, NULL, func, NULL);
	pthread_create(&th2, NULL, func, NULL);
	pthread_join(th1, NULL);
	pthread_join(th2, NULL);
	return 0;
}

两个线程同时执行func函数,错序打印1~499
如果用到pthread_create函数的第4个参数,这个参数的传入会反应到func中的形参中去。

void* func(void *args){
	int i;
	char *name = (char*)args;
	for(i=1; i<500; i++){
		printf("%s:%d\n", name, i);
	}
	return NULL;
}

int main(){
	pthread_t th1;
	pthread_t th2;
	pthread_create(&th1, NULL, func, "th1");
	pthread_create(&th2, NULL, func, "th2");
	pthread_join(th1, NULL);
	pthread_join(th2, NULL);
	return 0;
}

输出的结果,我们可以清晰地看出th1和th2的线程标记和交错运行。文章来源地址https://www.toymoban.com/news/detail-752558.html

到了这里,关于【C语言】多线程基本实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux入门之多线程|线程|进程基本概念及库函数

    目录 一、线程 1.线程的概念 补充知识点:页表 2.线程的优点 3.线程的缺点 4.线程异常 5.线程用途 二、线程与进程的区别与联系 三、关于进程线程的问题 0.posix线程库 1.创建线程 关于pthread_create的后两个参数 1.传入指针 2.传入对象 2.线程终止 3.取消线程 4.线程等待(等待线程

    2024年02月10日
    浏览(39)
  • 【Socket】Linux下UDP Socket的基本流程以及connect、bind函数的使用(C语言实现)

    Socket的原意是“插座”,在计算机通信领域,socket 被翻译为“套接字”。 Socket通信主要有两个类型:TCP、UDP。 TCP通信,是一个有序的、可靠的、面向连接的通信方式。用数据流的方式传递信息。 UDP通信,是无连接的、不保证有序到达的、但具有较好的实时性、能够高速传输

    2024年02月13日
    浏览(34)
  • 【Linux】C语言中多线程的创建、退出、回收、分离

    线程是轻量级的进程(LWP:light weight process),在 Linux 环境下线程的本质仍是进程。在计算机上运行的程序是一组指令及指令参数的组合,指令按照既定的逻辑控制计算机运行。操作系统会以进程为单位,分配系统资源,可以这样理解, 进程是资源分配的最小单位,线程是操

    2024年02月09日
    浏览(43)
  • 点亮现代编程语言的男人——C语言/UNIX之父Dennis Ritchie

    祝各位程序员们1024程序员节快乐🎉🎉🎉 图片来自网络,侵删 在程序员中,有一位人物的不被人熟知,他的贡献甚至比他自身更要出名 C语言之父,UNIX之父——Dennis MacAlistair Ritchie 一位该被所有程序员记住的男人 向Ritchie先生致敬🫡 他开发 C 语言的初衷,是在与Ken Thompso

    2024年02月06日
    浏览(33)
  • 了解Linux及Unix

    目前流行的服务器和 PC 端操作系统有 Linux、Windows、UNIX 等,手机操作系统有 Android、iOS、Windows Phone(简称 WP),嵌入式操作系统有 Windows CE、PalmOS、eCos、uClinux 等。 之前浅浅地接触了docker和k8s,运行环境为linux操作系统,就想着了解一下linux及unix,纯粹出于好奇哦,所以我准

    2024年02月12日
    浏览(36)
  • Linux--线程-条件控制实现线程的同步

    1.条件变量 条件变量是线程另一可用的同步机制。条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。 条件本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其他线程在获得互斥量之

    2024年02月05日
    浏览(40)
  • 【已解决】C语言实现多线程检索数据

    本博文源于笔者正在学习的c语言。学习如何用多线程进行检索数据。这里以检索一个数组的数据为例,给出代码,并分析如何进行线程通信,如果检索到,其余就别检索了。 想要用多线程检索数据 这段代码效果,通过全局变量作为线程通信的变量,实现了,查找成功就不让

    2024年01月17日
    浏览(35)
  • Linux C语言开发(十)vim基本操作

    目录 一.什么是vim 二.vim的进入与退出 三.vim的基本模式 四.vim的命令行模式操作

    2024年01月23日
    浏览(49)
  • Unix/Linux编程:UDS 数据报

    对于 recvfrom() 来讲,src_addr 和 addrlen 参数会返回用来发送数据报的远程 socket 的地址。 (这些参数类似于 accept() 中的 addr 和 addrlen 参数,它们返回已连接的对等 socket 的地址。) src_addr 参数是一个指针,它指向了一个与通信 domain 匹配的地址结构。与 accept() 一样, addrlen 是一

    2024年02月08日
    浏览(34)
  • 【Linux】线程终结篇:线程池以及线程池的实现

    linux线程完结 文章目录 前言 一、线程池的实现 二、了解性知识 1.其他常见的各种锁 2.读者写者问题 总结 什么是线程池呢? 线程池一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行

    2024年02月12日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包