C语言中的多线程简介

C语言中的多线程简介线程 Thread 专业术语称之为程序执行流的最小单元 线程是不会执行程序的 可以理解成线程就是一个载体 将要执行的代码运送到 CPU 进行处理 多线程就是多个线程同时并发执行 注意并发与并行的区别 并行同时执行不同的任务 并行是交替执行不同的任务 1 为什么要用多线程 1 避免阻塞单个线程中的程序 是按照顺序执行的 排在前面的程序如果发生异常卡住 阻塞 会影响到后面

线程 Thread

专业术语称之为程序执行流的最小单元 。线程是不会执行程序的,可以理解成线程就是一个载体,将 要执行的代码 运送到CPU进行处理。

多线程就是多个线程同时并发执行。

(注意并发与并行的区别,并行同时执行不同的任务,并行是交替执行不同的任务。)

1,为什么要用多线程?

1)避免阻塞

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

2)避免CPU的空转

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

3)提升效率

避免了1,2的问题,效率自然就提高了,归根结底也是为了这点。

2,线程与进程的区别

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

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

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

写了几个简单程序来说明一下,怎么使用线程,线程中的问题。

—————————————————————————————————————————————————

  • 创建一个线程,看 example0.c
#include 
  
    #include 
   
     #include 
    
      // 使用线程时需要添加 
     
       这个头文件 // myfunc 线程携带的函数;固定为Void*型 myfunc(void* args){ printf("hello world\n"); return NULL; } int main(){ // 声明线程th1 pthread_t th1; pthread_create(&th1,NULL,(void*)myfunc,NULL); pthread_join(th1,NULL); } 
      
     
    
  

C语言的程序中要使用线程,需要添加 这个头文件

myfunc()是这个线程要携带执行的程序 ,函数与函数的参数,都是必须是 void* 类 ,涉及到调用和传值时需要进行类型强转。


主函数中,先对线程进行一个声明, pthread_t th1;  使用 pthread_create()对th1进行创建。

pthread_create()中有4个参数

1要创建的线程id(th1);  2设置线程的属性(没有特殊需求时填NULL就可以了)  ; 3线程要运行的函数的地址(myfunc);4是要向运行的那个函数传参(myfunc()中的args),args 也是void*类型。

pthread_join()函数的功能是等待一个线程的结束,它是一个线程阻塞的函数。

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

运行这个程序看看

C语言中的多线程简介

如果没有pthread_join,终端可能会没有打印,因为主函数执行结束,线程中的函数还没有执行完成。

  • example1.c

一个多线程的例子,来演示说明下线程的是并发执行

// 创建两个线程 void* myfunc(void* args){ int i; 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(&th2,NULL,myfunc,"th2"); pthread_join(th1,NULL); pthread_join(th2,NULL); } 

两个线程,都去调用这个myfunc()函数,运行结果如下

C语言中的多线程简介

从这个程序的运行结果,能看出th1运行到17时,th2开始运行了,而这时th1没有继续打印,能看出th1与th2是并发执行的。两个线程的执行顺序是不确定的,重复运行example1.c这个程序的结果也会不同。

这个程序里使用到了pthread_create()中的第四个参数向 myfunc传参。

  • example3.c
int s =0; void* myfunc(void* args){ int i =0; for(i=0;i<10000;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; } // 用两个线程去执行myfunc函数 , 理想值应该为20000 

编译后运行结果如下

C语言中的多线程简介

这个函数里用了一个全局变量s,执行函数是一个10000的累加,理想的运行结果应该是20000,我这里运行了3次这个程序,每次的结果都不同。 用这个演示来表示一下,多线程之间是资源共享的。 

s++ 是有三个步的,读取s,s+1,写入s。 

在程序运行的某个时刻,th1携带myfunc执行s++,读取s,此时s=100,进行s+1, 与此同时th2也开始读取s,此时的s还是等于100, 这时th1,执行写入s=101,th2执行s++,写入s ,s=101. th2中的s 就会覆盖掉 th1中的s 。这样造成了结果的误差。

C语言中的多线程简介    手绘感受一下,绝对记忆深刻。·

这个问题有的名字 叫 race conditon

那么怎么解决这样问题呢? 应该听过一个词, 锁 

  • example4.c

对example3.c中的线程进行一个加锁

int s =0; pthread_mutex_t lock; //定义一个锁 void* myfunc(void* args){ pthread_mutex_lock(&lock); //上锁 int i =0; for(i=0;i<10000;i++){ s++; } pthread_mutex_unlock(&lock);//解锁 return NULL; } int main(){ pthread_t th1; pthread_t th2; pthread_mutex_init(&lock,NULL); //初始化lock这个锁 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; } 

运行结果

C语言中的多线程简介

锁的作用是什么呢? 

前面说过多线程是并发执行的,th1运行后 进行了加锁,th2这时候想要运行,就必须等待th1解锁之后才行。

C语言中的多线程简介

(一个卫生间,多个人要用,第一个人进去之后,把门锁上了,后边的人就得排队等着,第一个方便完了,解锁开门出来,第二个人进去,继续锁门……)

锁 在提高程序的安全性的同时,也降低了程序的效率。

锁的使用方法

pthread_mutex_t lock;  声明一个锁

pthread_mutex_init(&lock,NULL); 对声明的锁进行初始化

pthread_mutex_lock(&lock); //上锁 此时其他线程就开始等待

pthread_mutex_lock(&unlock); //解锁  其他线程可以使用资源了

死锁!

拿上边举例,th1运行后,th2会等待th1解锁,才能运行,如果程序出现错误中断了,th1没有执行完,重新启动后,th1又重新执行,这时候th2排在th1前边,需要等待th1的解锁 ,而th1又在等待th2结束。 这样就造成了相互等待的情况,这个就是死锁。

额外

多线程的目的

可以充分利用CPU资源,并发去做很多事,提高使用率,减少计算时间。

线程是不是越多越好?

    肯定不是,要考虑线程的创建时间+销毁时间 ,选择一个适当的数量。

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

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/177098.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午8:19
下一篇 2026年3月26日 下午8:19


相关推荐

  • MySQL与SqlServer的区别「建议收藏」

    MySQL与SqlServer的区别「建议收藏」一、MySQL与SqlServer的区别目前最流行的两种后台数据库即为Mysql和SQLServer。这两者最基本的相似之处在于数据存储和属于查询系统,你可以使用SQL来访问这两种数据库的数据,因为它们都支持ANSI-SQL(数据库管理标准)。还有,这两种数据库系统都支持二进制关键字和关键索引,这就大大地加快了查询速度。同时,二者也都提供支持XML的各种格式。根本的区别:SQL服务器的狭隘的、保守的存储引擎而MySQL服务器的可扩展、开放的存储引擎;SQL服务器的引擎是Sybase,而MyS

    2022年10月2日
    3
  • python数据库操作之sqlalchemy逆向工程

    python数据库操作之sqlalchemy逆向工程依赖安装pipinstallsqlacodegen数据库配置config.pyimportosHOST=’localhost’PORT=3306USERNAME=’root’PASSWORD=’root’DB=’demo’DB_URI=f’mysql+pymysql://{USERNAME}:{PASSWORD}@{HOST}:{PORT}/{DB}’#自动生成modelsos.system(f’sqlacodegen{DB_URI}

    2022年6月22日
    39
  • flex布局 文字水平垂直居中

    flex布局 文字水平垂直居中display flex align items center justify content center 就酱

    2026年3月19日
    2
  • 微服务 eureka_变压吸附的原理

    微服务 eureka_变压吸附的原理文章目录前言一、Eureka是什么?二、Eureka是干什么的?1.服务治理三、Eureka原理解析1.总体流程2.读入数据总结前言       随着互联网技术的发展,系统用户量的增长,很多系统都采用了分布式的方式进行部署。这个固然大大提高了系统的性能和可用性,但是分布式部署导致各种服务数量大增,这给我们进行服务治理和运维带来了困扰。一、Eureka是什么?     &nbsp

    2022年4月19日
    40
  • 达梦数据库分区表的使用

    达梦数据库分区表的使用前言在大型的企业应用或企业级的数据库应用中,要处理的数据量通常达到TB级,对于这样的大型表执行全表扫描或者DML操作时,效率是非常低的。为了提高数据库在大数据量读写操作和查询时的效率,达梦数据库提供了对表和索引进行分区的技术,把表和索引等数据库对象中的数据分割成小的单位,分别存放在一个个单独的段中,用户对表的访问转化为对较小段的访问,以改善大型应用系统的性能。达梦数据库分区表主要包括范围…

    2022年5月26日
    67
  • centos7安装python3.8_centos python3安装

    centos7安装python3.8_centos python3安装centos7自带版本是python2.7centos8是自带python3的如果要用的3.0以上的版本需要手动安装,下载地址:https://www.python.org/ftp/python/1、先查看系统python的位置在哪儿whereispythonpython2.7默认安装是在/usr/bin目录中,切换到/usr/bin/cd/usr/bin/llpython*从下面的图中我们可以看到,python指向的是python2,python2指向的是python2.7,因此

    2026年3月11日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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