《嵌入式linux应用程序开发标准教程》笔记——9.多线程编程
线程是轻量级进程,创建线程的开销要比进程小得多,在大型程序中应用广泛。
9.1 线程概述
- 进程包含自己的代码、数据、堆栈、资源等等,创建和切换的开销比较大;
- 线程是轻量级的进程,调度的最小单元,同一个进程内的线程可以共享资源;
- 线程的上下文开销比进程小得多;
- 线程有自己的堆栈,但是用户空间共享,例如一个线程修改全局变量,会影响到同一个进程内的另一个线程;

- linux里其实线程就是轻量级的进程,都用PCB表示,只不过新建线程时,共享同一个进程内的资源。线程应该是单独调度
9.2 线程编程
线程一般在用户空间操作,pthread线程库是通用的POXIS标准。
9.2.1 函数说明及示例
- 创建线程:pthread_create(), 确定线程的入口函数,创建后就开始执行;
- 退出线程:方式1,入口函数执行完后以后,自动结束;方式2,主动退出,pthead_exit().
- 释放资源:pthread_join(),类似wait(),也是阻塞的
- 终止别的进程:pthread_cancel(),终止别的线程;pthread_setcancel()/pthread_setcanceltype()设置是否允许别的线程结束自己及结束的方式等
【注意】:
- 线程如果调用exit(),会使整个进程退出。
#include <pthread.h> int pthread_create( pthread_t * thread, pthread_attr_t * attr, void *(*start_routine)(void *), void * arg );
参数:
thread,线程标识符
attr,线程属性
start_routine, 线程入口函数
arg,传递给入口函数的参数
返回值:
成功:0
出错:返回错误码 void pthread_exit( void * retval );
参数:
retval,线程结束时的返回值,由其他线程使用pthread_join()获取
【注意】:如果返回变量,不用使用线程的局部变量,因为推出以后,线程的堆栈空间可能被占用,导致返回值的变化。 可以返回绝对值或者变量值。 int pthread_join( pthread_t thread, void ** thread_return );
参数:
thread,要等待的线程标识符
thread_return, 被等待线程的返回值,实质是把 *thread_return = retval,其中retval是pthread_exit的指针,即传递了一个地址而已。可以利用传递的地址直接传递返回值,也可以利用变量传递,详见后面用例。
返回值:
成功:0
出错:返回错误码 int pthread_cancel( pthread_t thread )
参数:
thread,要取消的线程标识符
返回值:
成功:0
出错:返回错误码
//#define EXIT_USE_VER // 用变量返回,不可用线程自己的局部变量,因为退出以后有可能被复用
#define EXIT_USE_POINT // 用指针的赋值返回,适用于返回绝对值
#include <stdio.h> // printf
#include <stdlib.h> // exit
#include <unistd.h>
#include <sys/types.h> // pid_t
#include <fcntl.h>
#include <pthread.h>
#define MAX_DELAY_SECONDS 10.0
int rtn[3];
void fun_thread( int thread_no )
{
unsigned char count=0;
int delay_time=0;
printf("thread %d start.\r\n",thread_no);
for(count=0;count<3;count++)
{
delay_time = (int)(rand() * MAX_DELAY_SECONDS/RAND_MAX)+1;
sleep(delay_time);
printf("thread %d loop %dst delay second %d\r\n",thread_no,count,delay_time);
}
#ifdef EXIT_USE_VER
rtn[thread_no] = thread_no;
pthread_exit((void*)&rtn[thread_no]); // 不同线程退出值不同,仅测试用
#endif
#ifdef EXIT_USE_POINT
pthread_exit((void*)0xaa); // 可以返回绝对值
#endif
}
int main(int args, char *argv[])
{
unsigned thread_no;
pthread_t thread[3];
#ifdef EXIT_USE_VER
int * thread_rtn;
#endif
#ifdef EXIT_USE_POINT
void * thread_rtn;
#endif
printf("start.\r\n");
for( thread_no=0; thread_no<3; thread_no++ )
{
if( pthread_create(&thread[thread_no],NULL,fun_thread,(void*)thread_no) <0 )
printf("Create thread %d err.\r\n",thread_no);
}
// exit(0); 不能有此函数,否则创建完线程以后,exit会把本进程及内部所有线程一并退出
// 方法1:等待输入后结束
// getchar();
// 方法2:用pthread_join()等待所有线程结束
for( thread_no=0; thread_no<3; thread_no++ )
{
#ifdef EXIT_USE_VER
if( pthread_join(thread[thread_no], (void**)&thread_rtn) < 0 )
#endif
#ifdef EXIT_USE_POINT
if( pthread_join(thread[thread_no], &thread_rtn) < 0 )
#endif
printf( "pthread_join:thread %d err.\r\n", thread_no);
else
#ifdef EXIT_USE_VER
printf( "pthread_join:thread %d status %d.\r\n", thread_no,*thread_rtn);
#endif
#ifdef EXIT_USE_POINT
printf( "pthread_join:thread %d status %d.\r\n", thread_no,(int)thread_rtn);
#endif
}
exit(0);
}
$ ./example
start.
thread 2 start.
thread 1 start.
thread 0 start.
thread 1 loop 0st delay second 4
thread 0 loop 0st delay second 8
thread 2 loop 0st delay second 9
thread 2 loop 1st delay second 2
thread 1 loop 1st delay second 8
thread 2 loop 2st delay second 4
thread 0 loop 1st delay second 10
thread 1 loop 2st delay second 8
thread 0 loop 2st delay second 3
pthread_join:thread 0 status 170.
pthread_join:thread 1 status 170.
pthread_join:thread 2 status 170.
9.2.2 线程之间的同步和互斥
互斥锁适用于只有1个共享资源;
信号量适用多个共享资源的同步。
9.2.2.1 互斥锁线程控制
- 互斥锁可简单理解成全局变量,只有上锁和解锁两种状态;
- 若干其他线程希望上锁一个已经被上锁的互斥锁,则该线程挂起;
- 相关函数:
- pthread_mutex_init(): 初始化
- pthread_mutex_lock():上锁
- pthread_mutex_trylock():判断上锁
- pthread_mutex_unlock():解锁
- pthread_mutex_destroy():清除
#include <pthread.h> int pthread_mutex_init( pthread_mutex_t * mutex, const pthread_mutexattr_t * mutexattr );
参数:
mutex,互斥锁;
mutexattr,互斥锁种类,若为NULL,则使用默认的互斥锁类型。具体的锁类型后续研究吧
返回值:
成功:0
出错:返回错误码 int pthread_mutex_lock( pthread_mutex_t * mutex );
int phtread_mutex_trylock( pthread_mutex_t * mutex );
int pthread_mutex_unlock( pthread_mutex_t * mutex );
int pthread_mutex_unlock( pthread_mutex_t * mutex );
参数:
mutex,互斥锁
返回值:
成功:0
出错:-1
增加互斥锁,使原本独立的无序程序按设计预期运行
/* 9-1,pthread_mutex */
//#define EXIT_USE_VER // 用变量返回,不可用线程自己的局部变量,因为退出以后有可能被复用
#define EXIT_USE_POINT // 用指针的赋值返回,适用于返回绝对值
#include <stdio.h> // printf
#include <stdlib.h> // exit
#include <unistd.h>
#include <sys/types.h> // pid_t
#include <fcntl.h>
#include <pthread.h>
#define MAX_DELAY_SECONDS 10.0
#ifdef EXIT_USE_VER
int rtn[3];
#endif
pthread_mutex_t g_mutex;
void fun_thread( int thread_no )
{
unsigned char count=0;
int delay_time=0;
pthread_mutex_lock(&g_mutex);
printf("thread %d start.\r\n",thread_no);
for(count=0;count<3;count++)
{
delay_time = (int)(rand() * MAX_DELAY_SECONDS/RAND_MAX)+1;
sleep(delay_time);
printf("thread %d loop %dst delay second %d\r\n",thread_no,count,delay_time);
}
pthread_mutex_unlock(&g_mutex);
#ifdef EXIT_USE_VER
rtn[thread_no] = thread_no;
pthread_exit((void*)&rtn[thread_no]); // 不同线程退出值不同,仅测试用
#endif
#ifdef EXIT_USE_POINT
pthread_exit((void*)0); // 可以返回绝对值
#endif
}
int main(int args, char *argv[])
{
unsigned thread_no;
pthread_t thread[3];
#ifdef EXIT_USE_VER
int * thread_rtn;
#endif
#ifdef EXIT_USE_POINT
void * thread_rtn;
#endif
pthread_mutex_init(&g_mutex,NULL);
printf("start.\r\n");
for( thread_no=0; thread_no<3; thread_no++ )
{
if( pthread_create(&thread[thread_no],NULL,fun_thread,(void*)thread_no) <0 )
printf("Create thread %d err.\r\n",thread_no);
}
// exit(0); 不能有此函数,否则创建完线程以后,exit会把本进程及内部所有线程一并退出
// 方法1:等待输入后结束
// getchar();
// 方法2:用pthread_join()等待所有线程结束
for( thread_no=0; thread_no<3; thread_no++ )
{
#ifdef EXIT_USE_VER
if( pthread_join(thread[thread_no], (void**)&thread_rtn) < 0 )
#endif
#ifdef EXIT_USE_POINT
if( pthread_join(thread[thread_no], &thread_rtn) < 0 )
#endif
printf( "pthread_join:thread %d err.\r\n", thread_no);
else
#ifdef EXIT_USE_VER
printf( "pthread_join:thread %d status %d.\r\n", thread_no,*thread_rtn);
#endif
#ifdef EXIT_USE_POINT
printf( "pthread_join:thread %d status %d.\r\n", thread_no,(int)thread_rtn);
#endif
}
pthread_mutex_destroy(&g_mutex);
exit(0);
}
$ ./example
start.
thread 2 start.
thread 2 loop 0st delay second 9
thread 2 loop 1st delay second 4
thread 2 loop 2st delay second 8
thread 1 start.
thread 1 loop 0st delay second 8
thread 1 loop 1st delay second 10
thread 1 loop 2st delay second 2
thread 0 start.
thread 0 loop 0st delay second 4
thread 0 loop 1st delay second 8
thread 0 loop 2st delay second 3
pthread_join:thread 0 status 0.
pthread_join:thread 1 status 0.
pthread_join:thread 2 status 0.
9.2.2.2 信号量线程控制
- sem,本质上是非负的整数计数器
- P:sem-1
- V: sem+1
- sem>=0,进程/线程具有公共资源的访问权
- sem<0,进程/线程没有公共资源访问权,阻塞,指导sem>=0为止
- 用途:同步和互斥,若互斥,往往只有1个sem;同步时,往往有多个sem,安排不同初值来实现顺序执行
- 线程和进程的sem接口函数不同

互斥流程

#include <semaphore.h> int sem_init( sem_t *sem, int pshared, unsigned int value ); // 创建信号量,并初始化它的值
参数:
sem,信号量指针
pshared,决定信号量能在几个进程间共享,linux不支持,只能用0;
value,信号量初始值
返回值:
成功:0
出错:-1 int sem_wait( sem_t *sem ); // P操作,阻塞
int sem_trywait( sem_t *sem ); // P操作,不阻塞
int sem_post( sem_t *sem ); // V操作
int sem_getvalue(set_t * sem ); // 得到信号量的值
int sem_destroy(sem_t * sem ); // 删除信号量
参数:
sem,信号量指针
返回值:
成功:0
出错:-1
用信号量实现3个进程的有序执行,倒着来,2->1->0
/* 9-3,sem */
#include <stdio.h> // printf
#include <stdlib.h> // exit
#include <unistd.h>
#include <sys/types.h> // pid_t
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#define MAX_DELAY_SECONDS 2.0
sem_t g_sem[3];
void fun_thread( int thread_no )
{
unsigned char count=0;
int delay_time=0;
sem_wait(&g_sem[thread_no]);
printf("thread %d start.\r\n",thread_no);
for(count=0;count<3;count++)
{
delay_time = (int)(rand() * MAX_DELAY_SECONDS/RAND_MAX)+1;
sleep(delay_time);
printf("thread %d loop %dst delay second %d\r\n",thread_no,count,delay_time);
}
if( thread_no!=0 )
sem_post(&g_sem[thread_no-1]);
pthread_exit((void*)0); // 可以返回绝对值
}
int main(int args, char *argv[])
{
unsigned thread_no;
pthread_t thread[3];
void * thread_rtn;
printf("start.\r\n");
sem_init(&g_sem[0], 0,0);
sem_init(&g_sem[1], 0,0);
sem_init(&g_sem[2], 0,1); // 允许2先运行
for( thread_no=0; thread_no<3; thread_no++ )
{
if( pthread_create(&thread[thread_no],NULL,fun_thread,(void*)thread_no) <0 )
printf("Create thread %d err.\r\n",thread_no);
}
// exit(0); 不能有此函数,否则创建完线程以后,exit会把本进程及内部所有线程一并退出
// 方法1:等待输入后结束
// getchar();
// 方法2:用pthread_join()等待所有线程结束
for( thread_no=0; thread_no<3; thread_no++ )
{
if( pthread_join(thread[thread_no], &thread_rtn) < 0 )
printf( "pthread_join:thread %d err.\r\n", thread_no);
else
printf( "pthread_join:thread %d status %d.\r\n", thread_no,(int)thread_rtn);
}
sem_destroy(&g_sem[0]);
sem_destroy(&g_sem[1]);
sem_destroy(&g_sem[2]);
exit(0);
}
$ ./example
start.
thread 2 start.
thread 2 loop 0st delay second 2
thread 2 loop 1st delay second 1
thread 2 loop 2st delay second 2
thread 1 start.
thread 1 loop 0st delay second 2
thread 1 loop 1st delay second 2
thread 1 loop 2st delay second 1
thread 0 start.
thread 0 loop 0st delay second 1
thread 0 loop 1st delay second 2
thread 0 loop 2st delay second 1
pthread_join:thread 0 status 0.
pthread_join:thread 1 status 0.
pthread_join:thread 2 status 0.
9.2.3 线程属性
- pthread_create()的第二个参数可以设定线程属性,包括绑定、分离、堆栈、优先级等;
- 绑定属性,内核线程与用户线程一对一,linux用内核线程调度,如果绑定,能够保证需要时总能有对应的内核线程;如果不绑定,需要时系统分配内核线程;比较重要的线程倒是可以考虑设置绑定属性
- 分离属性,默认非分离,线程结束也留有一定的资源,pthread_join以后才彻底释放;分离,执行结束马上释放,有问题,例如执行很快,可能在create之前线程就结束返回了,此时连线程号都释放了,可能导致出错。完全没必要设置分离属性。
#include <pthread.h> int pthread_attr_init( pthread_attr_t * attr ); // 初始化
参数:
attr,线程属性
返回值:
成功:0
出错:返回错误码 int pthread_attr_setscope( pthread_attr_t * attr, int scope ); // 是否绑定
参数:
attr,线程属性
scope,PTHREAD_SCOPE_SYSTEM:绑定
PTHREAD_SCOPE_PROCESS:非绑定
返回值:
成功:0
出错:-1
int pthread_attr_setdetachstate( pthread_attr_t * attr, int detachstate ); // 是否分离
参数:
attr,线程属性
scope,PTHREAD_CREATE_DETACHED:分离
PTHREAD_SCOPE_JOINABLE:非分离
返回值:
成功:0
出错:-1
int pthread_attr_getschedparam( pthread_attr_t * attr, struct sched_param * param ); // 是否分离
参数:
attr,线程属性
param,线程优先级
返回值:
成功:0
出错:-1
int pthread_attr_setschedparam( pthread_attr_t * attr, struct sched_param * param ); // 是否分离
参数:
attr,线程属性
param,线程优先级
返回值:
成功:0
出错:-1
《嵌入式linux应用程序开发标准教程》笔记——9.多线程编程的更多相关文章
- 《嵌入式linux应用程序开发标准教程》笔记——6.文件IO编程
前段时间看APUE,确实比较详细,不过过于详细了,当成工具书倒是比较合适,还是读一读这种培训机构的书籍,进度会比较快,遇到问题时再回去翻翻APUE,这样的效率可能更高一些. <嵌入式linux应 ...
- 《嵌入式linux应用程序开发标准教程》笔记——8.进程间通信
, 8.1 概述 linux里使用较多的进程间通信方式: 管道,pipe和fifo,管道pipe没有实体文件,只能用于具有亲缘关系的进程间通信:有名管道 named pipe,也叫fifo,还允许无亲 ...
- 《嵌入式linux应用程序开发标准教程》笔记——7.进程控制开发
进程是系统资源的最小单元,很重要. 7.1 linux进程的基本概念 定义:一个程序的一次执行过程,同时也是资源分配的最小单元.程序是静态的,而进程是动态的. 进程控制块:linux系统用进程控制块描 ...
- 嵌入式Linux应用程序开发详解------(创建守护进程)
嵌入式Linux应用程序开发详解 华清远见 本文只是阅读文摘. 创建一个守护进程的步骤: 1.创建一个子进程,然后退出父进程: 2.在子进程中使用创建新会话---setsid(): 3.改变当前工作目 ...
- 嵌入式Linux应用程序开发环境搭建记录
2016年2月 参考资料: OK210软件手册(Linux版).pdf Ubuntu下Qt4.7.1编译环境配置说明.pdf 我阅读了以下内容: OK210软件手册(Linux版).pdf 第七章 O ...
- 嵌入式linux应用程序调试方法
嵌入式linux应用程序调试方法 四 内存工具 五 C/C++代码覆盖.性能profiling工具 四 内存工具 您肯定不想陷入类似在几千次调用之后发生分配溢出这样的情形. 许多小组花了许许多多时间来 ...
- gdbserver远程调试嵌入式linux应用程序方法
此处所讲的是基于gdb和gdbsever的远程调试方法.环境为:PC机:win7.虚拟机:10.04.下位机:飞嵌TE2440开发板. 嵌入式linux应用程序的开发一般都是在linux里面编写好代码 ...
- Linux入门-程序开发
Linux程序开发 linux程序总体上来说是分两部分的: 1. 底层驱动程序开发: 2.应用层应用程序开发: 驱动程序 一般情况下驱动是跟内核与硬件有关系的,编程语言是C语言,需要懂一些硬件的知识, ...
- 嵌入式linux系统应用开发
关于嵌入式系统 平时大家说的嵌入式其实范围比较广的,是一种软硬件可裁剪,以应用为中心开发的专用系统,硬件平台可以是单片机,或者以ARM系列的处理器.单片机一般直接裸奔程序,不过现在有了好多基于单片 ...
随机推荐
- cmd,bat和dos的区别
区别 dos是磁盘操作系统(Disk Operating System),是个人计算机上的一类操作系统. bat是DOS命令,在任何dos环境下都可以使用. bat文件是dos下的批处理文件,批处理文 ...
- Codeforces 1142B(倍增)
1.先预处理出在循环中某数前面的数是谁. 2.读入a数列时贪心选取最晚的父亲. 3.链上倍增预处理二进制祖先. 4.对于每个位置,预处理第n-1个祖先位置最早要从哪里开始,技巧上再顺手与前一位的最早位 ...
- mongodb-安装&配置&启动
下载:https://www.mongodb.com/dr/fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-4.0.3.tgz/downloa ...
- 113 Path Sum II 路径总和 II
给定一个二叉树和一个和,找到所有从根到叶路径总和等于给定总和的路径.例如,给定下面的二叉树和 sum = 22, 5 / \ 4 ...
- sizeof()用法汇总 zhuan
sizeof()功能:计算数据空间的字节数1.与strlen()比较 strlen()计算字符数组的字符数,以"\0"为结束判断,不计算为'\0'的数组元素. ...
- 三行命令搞定查询Python安装目录
想为Python添加一个库文件到默认目录,却忘记了Python安装目录. 其实,只要用下面三行命令,就可以轻松得到Python安装路径了. 进入Python >>>import sy ...
- 用YII实现多重查询(基于tag)
场景: 有一个饭店表 restaurant,存放所有饭店记录.我需要一个功能,将饭店按照不同的条件进行多重查询.就象这样: 氛围:浪漫 / 商务会谈 / 茅草屋 菜系:川菜 / 鲁菜 / 家常菜. ...
- 【数据库-Azure SQL Database】JDBC 如何连接 SQL Azure 数据库
使用 JAVA 代码连接 Azure SQL Database 时产生了 SSL 错误,对于此问题大多数用户都是因为不知如何编写 JDBC 连接字符串而产生的,以下为相关示例代码,供您参考: pa ...
- SqlServer表和excel数据批量复制方法
SqlServer表和excel数据批量复制方法 一.SqlServer表数据复制到excel方法: 1.新建查询,用sql语句把表数据读出来 2.然后,选择数据,右键“复制”(如果需要表字段名称,则 ...
- 精仿百思不得姐客户端应用iOS源码
XFBaiSiBuDeJie 高仿百思不得姐客户端 初次学习使用RAC,还不是怎么熟悉,使用的仍是MVC模式,MVVM还在摸索中... 如果大家觉得还不错,请给颗星星支持下~~~ 程序中使用到的库 A ...