【C编程基础】多线程编程
基础知识
1.基本概念
(1)线程,即轻量级进程(LWP:LightWeight Process),是程序执行流的最小单元。 线程是进程中的一个实体,是被系统独立调度和分派的基本单位。
(2)线程同步,就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。这里的同步千万不要理解成那个同时进行,应是指协同、协助、互相配合。
(3)线程互斥,是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步。
2.三种基本状态
就绪状态,指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;
运行状态,指线程占有处理机正在运行;
阻塞状态,指线程在等待一个事件(如信号量),逻辑上不可执行。
3.进程和线程的关系
简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。
进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。
线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。
对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
4.线程同步互斥的4种方式
(1)临界区(Critical Section):适合一个进程内的多线程访问公共区域或代码段时使用
(2)互斥量 (Mutex):适合不同进程内多线程访问公共区域或代码段时使用,与临界区相似。
(3)事件(Event):通过线程间触发事件实现同步互斥
(4)信号量(Semaphore):与临界区和互斥量不同,可以实现多个线程同时访问公共区域数据,原理与操作系统中PV操作类似,先设置一个访问公共区域的线程最大连接数,每有一个线程访问共享区资源数就减一,直到资源数小于等于零。
#include <pthread.h>多线程函数
线程按照其调度者可以分为用户级线程和核心级线程两种 。用户级线程主要解决的是上下文切换的问题,它的调度算法和调度过程全部由用户自行选择决定,在运行时不需要特定的内核支持;
我们常用基本就是用户级线程,总结一下POSIX提供的用户级线程接口。
基本线程操作相关的函数:
1.线程的建立结束
函数 | 说明 |
---|---|
pthread_create() | 创建线程开始运行相关线程函数,运行结束则线程退出 |
pthread_eixt() | 因为exit()是用来结束进程的,所以则需要使用特定结束线程的函数 |
pthread_join() | 挂起当前线程,用于阻塞式地等待线程结束,如果线程已结束则立即返回,0=成功 |
pthread_cancel() | 发送终止信号给thread线程,成功返回0,但是成功并不意味着thread会终止 |
pthread_testcancel() | 在不包含取消点,但是又需要取消点的地方创建一个取消点,以便在一个没有包含取消点的执行代码线程中响应取消请求. |
pthread_setcancelstate() | 设置本线程对Cancel信号的反应 |
pthread_setcanceltype() | 设置取消状态 继续运行至下一个取消点再退出或者是立即执行取消动作 |
pthread_setcancel() | 设置取消状态 |
2.线程的互斥和同步
函数 | 说明 |
---|---|
pthread_mutex_init() | 互斥锁的初始化 |
pthread_mutex_lock() | 锁定互斥锁,如果尝试锁定已经被上锁的互斥锁则阻塞至可用为止 |
pthread_mutex_trylock() | 非阻塞的锁定互斥锁 |
pthread_mutex_unlock() | 释放互斥锁 |
pthread_mutex_destory() | 互斥锁销毁函数 |
3.使用信号量控制线程 (默认无名信号量)
函数 | 说明 |
---|---|
sem_init(sem) | 初始化一个定位在sem的匿名信号量 |
sem_wait() | 把信号量减1操作,如果信号量的当前值为0则进入阻塞,为原子操作 |
sem_trywait() | 如果信号量的当前值为0则返回错误而不是阻塞调用(errno=EAGAIN),其实是sem_wait()的非阻塞版本 |
sem_post() | 给信号量的值加1,它是一个“原子操作”,即同时对同一个信号量做加1,操作的两个线程是不会冲突的 |
sem_getvalue(sval) | 把sem指向的信号量当前值放置在sval指向的整数上 |
sem_destory(sem) | 销毁由sem指向的匿名信号量 |
4.线程的基本属性配置
函数 | 说明 |
---|---|
pthread_attr_init() | 初始化配置一个线程对象的属性,需要用pthread_attr_destroy函数去除已有属性 |
pthread_attr_setscope() | 设置线程属性 |
pthread_attr_setschedparam() | 设置线程优先级 |
pthread_attr_getschedparam() | 获取线程优先级 |
注意:使用线程,编译时需要引用软件库lpthread,如下:
gcc -o thread thread.c -lpthread
/* thread.c */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h> #define THREAD_NUMBER 3 /*线程数*/
#define REPEAT_NUMBER 5 /*每个线程中的小任务数*/
#define DELAY_TIME_LEVELS 10.0 /*小任务之间的最大时间间隔*/
//
void * thrd_func(void *arg) {
/* 线程函数例程 */
int thrd_num = (long)arg;
int delay_time = ;
int count = ;
printf("Thread %d is starting\n", thrd_num);
for (count = ; count < REPEAT_NUMBER; count++) {
delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + ;
sleep(delay_time);
printf("\tThread %d: job %d delay = %d\n",thrd_num, count, delay_time);
} printf("Thread %d finished\n", thrd_num);
pthread_exit(NULL);
} int main(void) {
pthread_t thread[THREAD_NUMBER];
int no = , res;
void * thrd_ret;
srand(time(NULL));
for (no = ; no < THREAD_NUMBER; no++) {
/* 创建多线程 */
res = pthread_create(&thread[no], NULL, thrd_func, (void*) (long) no);
if (res != ) {
printf("Create thread %d failed\n", no);
exit(res);
}
} printf("Create treads success\n Waiting for threads to finish...\n");
for (no = ; no < THREAD_NUMBER; no++) {
/* 等待线程结束 */
res = pthread_join(thread[no], &thrd_ret);
if (!res) {
printf("Thread %d joined\n", no);
} else {
printf("Thread %d join failed\n", no);
}
}
return ;
} 例程中循环3次建立3条线程,并且使用pthread_join函数依次等待线程结束;
线程中使用rand()获取随机值随机休眠5次,随意会出现后执行的线程先执行完成; 可以看到,线程1先于线程0执行,但是pthread_join的调用时间顺序,先等待线程0执行;
由于线程1已经早结束,所以线程0被pthread_join等到的时候,线程1已结束,就在等待到线程1时,直接返回; 执行结果如下:
Create treads success
Waiting for threads to finish...
Thread is starting
Thread is starting
Thread is starting
Thread : job delay =
Thread : job delay =
Thread : job delay =
Thread : job delay =
Thread : job delay =
Thread : job delay =
Thread : job delay =
Thread : job delay =
Thread : job delay =
Thread : job delay =
Thread : job delay =
Thread : job delay =
Thread finished
Thread : job delay =
Thread finished
Thread joined
Thread joined
Thread : job delay =
Thread : job delay =
Thread finished
Thread joined
基本线程建立示例
gcc -o thread_mutex thread_mutex.c -lpthread
/*thread_mutex.c*/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h> #define THREAD_NUMBER 3 /* 线程数 */
#define REPEAT_NUMBER 3 /* 每个线程的小任务数 */
#define DELAY_TIME_LEVELS 10.0 /*小任务之间的最大时间间隔*/
pthread_mutex_t mutex; void *thrd_func(void *arg) {
int thrd_num = (int)(long) arg;
int delay_time = , count = ;
int res;
/* 互斥锁上锁 */
res = pthread_mutex_lock(&mutex);
if (res) {
printf("Thread %d lock failed\n", thrd_num);
pthread_exit(NULL);
}
printf("Thread %d lock \n", thrd_num);
printf("Thread %d is starting\n", thrd_num);
for (count = ; count < REPEAT_NUMBER; count++) {
delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + ;
sleep(delay_time);
printf("\tThread %d: job %d delay = %d\n",
thrd_num, count, delay_time);
}
printf("Thread %d finished\n", thrd_num);
res = pthread_mutex_unlock(&mutex);
if (res) {
printf("Thread %d unlock failed\n", thrd_num);
pthread_exit(NULL);
}
printf("Thread %d unlock\n", thrd_num); pthread_exit(NULL);
} int main(void) {
pthread_t thread[THREAD_NUMBER];
int no = , res;
void * thrd_ret; srand(time(NULL));
/* 互斥锁初始化 */
pthread_mutex_init(&mutex, NULL);
for (no = ; no < THREAD_NUMBER; no++) {
res = pthread_create(&thread[no], NULL, thrd_func, (void*)(long)no);
if (res != ) {
printf("Create thread %d failed\n", no);
exit(res);
}
}
printf("Create treads success\n Waiting for threads to finish...\n");
for (no = ; no < THREAD_NUMBER; no++) {
res = pthread_join(thread[no], &thrd_ret);
if (!res) {
printf("Thread %d joined\n", no);
} else {
printf("Thread %d join failed\n", no);
}
}
/****互斥锁解锁***/
pthread_mutex_destroy(&mutex);
return ;
} 添加同步锁pthread_mutex_t
在线程中加入,于是程序在执行线程程序时;
调用pthread_mutex_lock上锁,发现上锁时候后进入等待,等待锁再次释放后重新上锁;
所以线程程序加载到队列中等待,等待成功上锁后继续执行程序代码; 运行结果:
Create treads success
Waiting for threads to finish...
Thread lock
Thread is starting
Thread : job delay =
Thread : job delay =
Thread : job delay =
Thread finished
Thread unlock
Thread lock
Thread is starting
Thread : job delay =
Thread : job delay =
Thread : job delay =
Thread finished
Thread unlock
Thread lock
Thread is starting
Thread : job delay =
Thread : job delay =
Thread : job delay =
Thread finished
Thread unlock
Thread joined
Thread joined
Thread joined
实现线程同步(互斥量)
1.创建线程
int pthread_create(pthread_t *restrict_ptid,const pthread_attr_t *restrict_attr,void *(*start_routine)(void*), void *restrict_arg);
(1)参数说明:
- ptid是一个pthread_t *类型的指针,pthread_t是类似pid_t的数据结构,表示线程ID;
- attr指明线程创建属性,如果为NULL就使用系统默认属性;
- start_routine是线程的主函数,它的参数是void *类型的指针,返回值也是void *类型的指针;
- arg是线程创建者传递给新建线程的参数,也就是start_routine的参数,如果需要向start_routine函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为restrict_arg的参数传入。
(2)返回值:
若线程创建成功,则返回0。若线程创建失败,则返回出错编号
(3)注意事项:
线程创建者和新建线程之间没有fork()调用那样的父子关系,它们是对等关系。调用pthread_create()创建线程后,线程创建者和新建线程哪个先运行是不确定的,特别是在多处理机器上。
2.终止线程
void pthread_exit(void *value_ptr);
(1)参数说明:value_ptr作为线程的返回值被调用pthread_join的线程使用。
(2)注意事项:由于一个进程中的多个线程是共享数据段的,因此通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放,但是可以用pthread_join()函数来同步并释放资源。
3.pthread_self():该函数返回调用线程的ID.这个数值与调用 pthread_create 创建本线程时使用的*thread 参数返回的值是一样的。
注意:Thread IDs 仅仅保证在一个进程内部的唯一性。当一个结束了的线程 joined(使用join等待一个线程结束)之后, 或者一个detached 状态的线程被结束 thread ID可能会被重新使用。 pthread_self()返回的线程ID与 调用 gettid()得到的内核线程ID是不一样的。
4.pthread_equal():比较线程ID,线程ID的大小没有意义。
引入原因:在线程中,线程ID的类型是pthread_t类型,由于在Linux下线程采用POSIX标准,所以,在不同的系统下,pthread_t的类型是不同的,比如在ubuntn下,是unsigned long类型,而在solaris系统中,是unsigned int类型。而在FreeBSD上才用的是结构题指针。 所以不能直接使用==判读,而应该使用pthread_equal来判断。
3.取消线程
int pthread_cancel(pthread_t thread);
注意:若是在整个程序退出时,要终止各个线程,应该在成功发送 CANCEL指令后,使用 pthread_join函数,等待指定的线程已经完全退出以后,再继续执行;否则,很容易产生 “段错误”。
4.连接线程(阻塞)
int pthread_join(pthread_t thread, void **value_ptr);
等待线程thread结束,并设置*value_ptr为thread的返回值。pthread_join阻塞调用者,一直到线程thread结束为止。当函数返回时,被等待线程的资源被收回。如果进程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。
线程终止有一下几种方法:
1.从主函数返回;
2.自己调用pthread_exit();
3.其他线程调用pthread_cancel();
4.线程所属的进程中任何线程调用exit()导致所有线程结束。
5.分离线程
int pthread_detach(pthread_t thread);
分离线程的语意是,线程thread结束后系统可以回收它的私有数据。
注释:pthread有两种状态joinable状态和unjoinable状态 一个线程默认的状态是joinable,如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。只有当你调用了pthread_join之后这些资源才会被释放。若是unjoinable状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放。unjoinable属性可以在pthread_create时指定,或在线程创建后在线程中pthread_detach自己,如:pthread_detach(pthread_self())或者父线程调用pthread_detach(thread_id)结束相应子进程。
参考文档:
【C编程基础】多线程编程的更多相关文章
- 大数据技术之_16_Scala学习_04_函数式编程-基础+面向对象编程-基础
第五章 函数式编程-基础5.1 函数式编程内容说明5.1.1 函数式编程内容5.1.2 函数式编程授课顺序5.2 函数式编程介绍5.2.1 几个概念的说明5.2.2 方法.函数.函数式编程和面向对象编 ...
- python --- 基础多线程编程
在python中进行多线程编程之前必须了解的问题: 1. 什么是线程? 答:线程是程序中一个单一的顺序控制流程.进程内一个相对独立的.可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程 ...
- Java多线程编程(2)--多线程编程中的挑战
一.串行.并发和并行 为了更清楚地解释这三个概念,我们来举一个例子.假设我们有A.B.C三项工作要做,那么我们有以下三种方式来完成这些工作: 第一种方式,先开始做工作A,完成之后再开始做工作B ...
- 进阶Java编程(1)多线程编程
Java多线程编程 1,进程与线程 在Java语言里面最大的特点是支持多线程的开发(也是为数不多支持多线程的编程语言Golang.Clojure方言.Elixir),所以在整个的Java技术学习里面, ...
- SDK编程之多线程编程
本课中,我们将学习如何进行多线程编程.另外我们还将学习如何在不同的线程间进行通信. 理论:前一课中,我们学习了进程,其中讲到每一个进程至少要有一个主线程.这个线程其实是进程执行的一条线索,除此主线程外 ...
- 廖雪峰Java13网络编程-1Socket编程-3TCP多线程编程
TCP多线程编程 一个ServerSocket可以和多个客户端同时建立连接,所以一个Server可以同时与多个客户端建立好的Socket进行双向通信. 因此服务器端,当我们打开一个Socket以后,通 ...
- Spark编程基础_RDD编程
RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变.可分区.里面的元素可并行计算的集合.RDD具有数据流模型的特 ...
- Java基础-多线程编程-1.随便选择两个城市作为预选旅游目标。实现两个独立的线程分别显示10次城市名,每次显示后休眠一段随机时间(1000ms以内),哪个先显示完毕,就决定去哪个城市。分别用Runnable接口和Thread类实现。
1.随便选择两个城市作为预选旅游目标.实现两个独立的线程分别显示10次城市名,每次显示后休眠一段随机时间(1000ms以内),哪个先显示完毕,就决定去哪个城市.分别用Runnable接口和Thread ...
- 网络编程基础--多线程---concurrent.futures 模块---事件Event---信号量Semaphore---定时器Timer---死锁现象 递归锁----线程队列queue
1 concurrent.futures 模块: # from abc import abstractmethod,ABCMeta # # class A(metaclass=ABCMeta): # ...
- 编程基础-msdn编程指南笔记
此博仅为笔记,摘自msdn编程指南文档,链接地址:http://msdn.microsoft.com/zh-cn/library/67ef8sbd.aspx 注释:// 单行注释 /* 多行注释*/ ...
随机推荐
- [转]nodeJS中redis初步使用
本文转自:https://blog.csdn.net/frankenjoy123/article/details/55209637 Node.js下使用Redis,首先: 1.有一台安装了Redis的 ...
- [android] 采用断点调试的方式观察pull解析的流程
当程序出现错误的时候,界面出不来,这个时候就需要调试技巧,描述这个程序在哪个地方出现的问题.在你认为可能出错的代码部分,左侧的行号栏点击打断点,在项目目录右键 ==>debug as ==> ...
- Mybatis foreach标签含义
背景 考虑以下场景: InfoTable(信息表): Name Gender Age Score 张三 男 21 90 李四 女 20 87 王五 男 22 92 赵六 女 19 94 孙七 女 23 ...
- Hibernate入门(三)
持久化类的编写规则: 1.持久化类需要提供无参的构造方法.因为在Hibernate的底层需要使用反射生成的实例. 2.持久化类的属性需要私有,对私有的属性提供共有的get和set方法.因为在Hiber ...
- String的坑
想必大家在熟悉不过了,不错今天就遇到了这个万年坑,哪怕喜欢翻源码的人,也不屑一顾翻它的源码,良言相劝最好翻下源码. 1. String为啥被定义为final ? 2. String是线程安全的么 ...
- PHP7.27: pdf
http://www.fpdf.org/ https://github.com/Setasign/FPDF https://www.ntaso.com/fpdf-and-chinese-charact ...
- JS与CSS阻止元素被选中及清除选中的方法总结
有时候,我们希望阻止用户选中我们指定区域的文字或内容. 举个栗子,有时候用户在一个区域执行频繁的点击操作,一不小心傲娇地点多了,就会选中当前区域的内容. 再举个栗子,制作轮播组件的时候,点击下一页,若 ...
- spring-boot-starter-thymeleaf对没有结束符的HTML5标签解析出错
springboot 在使用thymeleaf 作为模板时,当出现未关闭标签时,如下所示代码,标签没有关闭. <link href="plugin/layui/css/layui.cs ...
- Nginx 限制并发连接和并发请求数配置
Nginx限制并发连接和并发请求数配置 by:授客 QQ:1033553122 测试环境 nginx-1.10.0 配置介绍 查看是否内置模块 # pwd /mnt/nginx-1.10.0 ...
- drawable自定义字体颜色
一个很基础简单的问题,但是以前没用过,都是代码控制效果的,最近新的项目发现设置了color属性没效果,后来查了会资料才发现得单独设置,记录一下,虽然是小问题 上面的xml控制背景的变化,一开始我设置在 ...