15.Linux多线程编程

来源:互联网 发布:软件 程序员 编写 编辑:程序博客网 时间:2024/05/30 02:23

一、线程理论基础

(1)定义:线程技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者.传统的Unix也支持线程的概念,但是在一个进程中只

允许有一个线程,这样多线程就意味着多进程.现在,多线程技术已经被许多操作系统所支持,包括Windows/NT、Linux

(2)进程与线程的区别:

1.进程是一个具有一定独立功能的程序的一次运行活动,同时也是资源分配的最小单元;

进程是程序执行时的一个实例,即它是程序已经执行到某种程度的数据结构的汇集

从内核的观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的基本单位

Linux系统是一个多进程的系统,它的进程之间具有并行性、互不干扰等特点 也就是说,每个进程都是一个独立的运行单位,拥有各自的权利和责任

其中,各个进程都运行在独立的虚拟地址空间,因此,即使一个进程发生异常,它也不会影响到系统中的其他进程

2.线程是进程的一个执行流,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位

一个进程由几个线程组成(拥有很多相对独立的执行流的用户程序共享应用程序的大部分数据结构),线程与同属一个进程的其他的线程共享进程所拥有的全部资源

"进程——资源分配的最小单位,线程——程序执行的最小单位"

进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径

3.线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,

效率要差一些 但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程

进程有独立的地址空间,线程没有单独的地址空间(同一进程内的线程共享进程的地址空间)

(3)线程的优点

1.和进程相比,它是一种非常“节俭”的多任务操作方式.在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数

据段,这是一种"昂贵"的多任务工作方式运行于一个进程中的多个线程,它们之间使用相同的地址空间,而且线程间彼此切换所需的时间也远远小于进程间切换所需要的时间

据统计,一个进程的开销大约是一个线程开销的30倍左右

2.线程间方便的通信机制;对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过进程间通信的方式进行,这种方式不仅费时,而且很不方便;线程则不然,

由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便

3.除了以上所说的优点外,多线程程序作为一种多任务、并发的工作方式,有如下优点:

使多CPU系统更加有效.操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上

改善程序结构.一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改

二、多线程程序设计

(1)Linux系统下的多线程遵循POSIX线程接口,称为pthread 编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用libpthread.a

(2)创建

#include <pthread.h>
int pthread_create(pthread_t * tidp,const pthread_attr_t*attr,void*(*start_rtn)(void),void*arg)

tidp:线程id
attr: 线程属性(通常为空)
start_rtn:线程要执行的函数
arg:start_rtn的参数

(3)编译

因为pthread的库不是linux系统的库,所以在进行编译的时候要加上 -lpthread(gcc filename -lpthread)

(4)终止线程

如果进程中任何一个线程中调用exit或_exit,那么整个进程都会终止。线程的正常退出方式有:

1.线程从启动例程中返回

2. 线程可以被另一个进程终止

3. 线程自己调用pthread_exit函数

(5)线程退出

#include <pthread.h>
void pthread_exit(void * rval_ptr)

功能:终止调用线程

Rval_ptr:线程退出返回值的指针

(6)线程等待

#include <pthread.h>
int pthread_join(pthread_t tid,void **rval_ptr)

功能:阻塞调用线程,直到指定的线程终止

Tid :等待退出的线程id

Rval_ptr:线程退出的返回值的指针

三、线程同步

(1)进行多线程编程,因为无法知道哪个线程会在哪个时候对共享资源进行操作,因此让如何保护共享资源变得复杂,通过下面这些技术的使用,可以解决

线程之间对资源的竞争:1 互斥量Mutex  2 信号灯Semaphore  3 条件变量Conditions

(2)互斥量

1.定义

Item * p =queue_list;
Queue_list=queue_list->next;
process_job(p);
free(p);

当线程1处理完Item *p=queue_list后,系统停止线程1的运行,改而运行线程2;线程2照样取出头节点,然后进行处理,最后释放了该节点;过了段时间,线程1重新得到运

行;而这个时候,p所指向的节点已经被线程2释放掉,而线程1对此毫无知晓;他会接着运行process_job(p),而这将导致无法预料的后果

对于这种情况,系统给我们提供了互斥 量;线程在取出头节点前必须要等待互斥量,如果此时有其他线程已经获得该互斥量,那么该线程将会阻塞在这里;只有等到其他线程释放

掉该互斥量后,该线程才有可能得到该互斥量;互斥量从本质上说就是一把锁, 提供对共享资源的保护访问

2.创建

在Linux中, 互斥量使用类型pthread_mutex_t表示.在使用前, 要对它进行初始化:

对于静态分配的互斥量, 可以把它设置为默认的mutex对象PTHREAD_MUTEX_INITIALIZER

对于动态分配的互斥量, 在申请内存(malloc)之后, 通过pthread_mutex_init进行初始化, 并且在释放内存(free)前需要调用pthread_mutex_destroy

#include <pthread.h>
 int pthread_mutex_init(pthread_mutex_t 、*mutex,const pthread_mutexattr_t *attr)
 int pthread_mutex_destroy(pthread_mutex_t *mutex)

3.加锁

对共享资源的访问, 要使用互斥量进行加锁, 如果互斥量已经上了锁, 调用线程会阻塞, 直到互斥量被解锁

int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_trylock(pthread_mutex_t *mutex)

返回值: 成功则返回0, 出错则返回错误编号

trylock是非阻塞调用模式, 如果互斥量没被锁住, trylock函数将对互斥量加锁, 并获得对共享资源的访问权限; 

如果互斥量被锁住了,trylock函数将不会阻塞等待而直接返回EBUSY表示共享资源处于忙状态

4.解锁

在操作完成后,必须给互斥量解锁,也就是前面所说的释放。这样其他等待该锁的线程才有机会获得该锁,否则其他线程将会永远阻塞

int pthread_mutex_unlock(pthread_mutex_t *mutex)

(3)互斥量PK信号量

Mutex是一把钥匙,一个人拿了就可进入一个房间,出来的时候把钥匙交给队列的第一个

Semaphore是一件可以容纳N人的房间,如果人不满就可以进去,如果人满了,就要等待有人出来 对于N=1的情况,称为binary semaphore

Binary semaphore与Mutex的差异:

1. mutex要由获得锁的线程来释放(谁获得,谁释放)。而semaphore可以由其它线程释放

2. 初始状态可能不一样:mutex的初始值是1 ,semaphore的初始值可能是0(或者为1)