【嵌入式学习历程15】多线程编程

什么是线程
线程是进程中执行运算的最小单位,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。如果把进程理解为在逻辑上操作系统所完成的任务,那么线程表示完成该任务的许多可能的子任务之一。线程自己基本不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但它可以与同属一个进程的其他线程共享进程所拥有的全部资源。

线程和进程的区别与联系
1、进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程是资源分配的最小单元;线程是CPU调度和分派的基本单位(程序执行的最小单位)。
2、进程有独立的地址空间,线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间。
3、一个进程崩溃后,在保护模式下不会对其它进程产生影响,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮。
4、在进程切换时,耗费资源较大,效率要差一些;但是线程耗费资源少,效率高。
5、线程的划分尺度小于进程,使得多线程程序的并发性高。
6、线程在执行过程中与进程也是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
7、一个程序至少有一个进程,一个进程至少有一个线程.
8、 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这也是进程和线程的重要区别。

多线程的优点
多线程处理可以同时运行多个线程。由于多线程应用程序将程序划分成多个独立的任务,因此可以在以下方面显著提高性能:
(1)多线程技术使程序的响应速度更快 ,因为用户界面可以在进行其它工作的同时一直处于活动状态;
(2)当前没有进行处理的任务时可以将处理器时间让给其它任务;
(3)占用大量处理时间的任务可以定期将处理器时间让给其它任务;
(4)可以随时停止任务;
(5)可以分别设置各个任务的优先级以优化性能。

是否需要创建多个线程取决于各种因素。在以下情况下,最适合采用多线程处理:
(1)耗时或大量占用处理器的任务阻塞用户界面操作;
(2)各个任务必须等待外部资源 (如远程文件或 Internet连接)

多线程的缺点
同样的 ,多线程也存在许多缺点 ,在考虑多线程时需要进行充分的考虑。多线程的主要缺点包括:
(1)等候使用共享资源时造成程序的运行速度变慢。这些共享资源主要是独占性的资源 ,如打印机等。
(2)对线程进行管理要求额外的 CPU开销。线程的使用会给系统带来上下文切换的额外负担。当这种负担超过一定程度时,多线程的特点主要表现在其缺点上,比如用独立的线程来更新数组内每个元素。
(3)线程的死锁。即较长时间的等待或资源竞争以及死锁等多线程症状。
(4)对公有变量的同时读或写。当多个线程需要对公有变量进行写操作时,后一个线程往往会修改掉前一个线程存放的数据,从而使前一个线程的参数被修改;另外 ,当公用变量的读写操作是非原子性时,在不同的机器上,中断时间的不确定性,会导致数据在一个线程内的操作产生错误,从而产生莫名其妙的错误,而这种错误是程序员无法预知的。

多线程的实现
*多线程开发的最基本概念包括:线程、互斥锁、条件。
*线程包括:创建、退出、等待。
*互斥锁包括:创建、销毁、加锁、解锁。
*条件包括:创建、销毁、触发、广播、等待。

1.1 线程的创建
定义: int pthread_create(pthread_t restrict thread,const pthread_attr_t *restrict attr,void(start_routine)(void), void *restrict arg);
参数说明:
pthread_t *restrict thread:线程号
const pthread_attr_t *restrict attr:属性、一般为NULL
void*(start_routine)(void):线程函数
void *restrict arg:arg为线程函数的形参,如果没有写NULL

1.2 线程的退出
pthread_exit函数用于终止线程。
线程退出有三种方式:
*线程从执行函数返回,返回值是线程的退出码。
*线程被同一进程的其他线程取消。
*调用pthread_exit()函数退出。

1.3 线程的等待
pthread_join函数用于等待线程。阻塞调用线程,直到指定的线程终止。
函数定义:int pthread_join(pthread_t thread, void **value_ptr);
参数说明:
pthread_t thread:线程号
void **value_ptr:线程退出状态
注: pthread_join()仅适用于非分离的目标进程。如果没有必要等待特定线程终止之后才进行其他处理,则应当将该线程分离。

线程同步
进行多线程编程,因为无法知道哪个线程会在哪个时候对共享资源进行操作,因此让如何保护共享资源变得复杂,通过下面这些技术的使用,可以解决
线程之间对资源的竞争:
1 互斥量Mutex
2 信号灯Semaphore
3 条件变量Conditions

为什么需要互斥锁(互斥量)?
在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为“互斥锁” 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
2.1 互斥量的创建
在Linux中, 互斥量使用类型pthread_mutex_t表示.在使用前, 要对它进行初始化:
对于静态分配的互斥量, 可以把它设置为默认的mutex对象PTHREAD_MUTEX_INITIALIZER
对于动态分配的互斥量, 在申请内存(malloc)之后, 通过pthread_mutex_init进行初始化, 并且在释放内存(free)前需要调用pthread_mutex_destroy
2.2 加锁
对共享资源的访问, 要使用互斥量进行加锁, 如果互斥量已经上了锁, 调用线程会阻塞, 直到互斥量被解锁。
int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_trylock(pthread_mutex_t *mutex)
返回值: 成功则返回0, 出错则返回错误编号。
trylock是非阻塞调用模式, 如果互斥量没被锁住, trylock函数将对互斥量加锁, 并获得对共享资源的访问权限; 如果互斥量被锁住了,trylock函数将不会阻塞等待而直接返回EBUSY, 表示共享资源处于忙状态
2.3 解锁
在操作完成后,必须给互斥量解锁,也就是前面所说的释放。这样其他等待该锁的线程才有机会获得该锁,否则其他线程将会永远阻塞。
int pthread_mutex_unlock(pthread_mutex_t *mutex)
2.4 销毁
int pthread_cond_destroy(pthread_cond_t *cond);

条件变量Conditions
条件变量(condition variable)是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待某个条件为真,而将自己挂起;另一个线程使的条件成立,并通知等待的线程继续。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
3.1 条件变量的创建
条件变量的初始化也有两种方式,一种函数(动态)、一种宏(静态)
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
3.2 销毁
int pthread_cond_destroy(pthread_cond_t *cond);
3.3 触发(将线程挂起)
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数说明:
pthread_cond_t *restrict cond:定义的条件变量
pthread_mutex_t *restrict mutex:解锁操作
3.4 等待
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);//等待
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);//有时等待

无论是哪种等待方式,都必须与一个互斥锁配合,以防止多个线程同时请求 pthread_cond_wait()(或pthread_cond_timedwait())

信号量与互斥量
互斥量:保护资源,线程互斥;一次只能有一个访问共享资源;初始值为1;互斥量要由获得锁的线程来释放(谁获得,谁释放)
信号量(信号灯):可允许多个线程依次有序访问;初始值为0或1,取决于是计算信号量还是二值信号量;可以由其它线程释放

二值信号灯:信号灯的值只能取0或1,类似于互斥锁。 但两者有不同:
*信号灯强调共享资源,只要共享资源可用,其他进程同样可以修改信号灯的值;
*互斥锁更强调进程,占用资源的进程使用完资源后,必须由进程本身来解锁。
* 计数信号灯:信号灯的值可以取任意非负值

你可能感兴趣的:(学习记录)