一:进程是系统资源分配的基本单位,线程是程序独立运行的基本单位,多线程之间可以共享资源,多线程之间线程的切换花费的代价较低。
在Linux系统下启动一个新的进程必须分配给他独立的地址空间,建立众多的数据表维护它的代码段、堆栈段和数据段,这是一个“昂贵”的多任务工作方式。而运行于一个进程中的多线程,它们彼此之间使用相同的地址空间,共享大部分数据段,启动一个进程花费的空间远远大于一个线程,据统计,总的来说一个进程的开销大约是一个线程的30倍。
使用多线程间方便的通信机制,对不同进程来说,它们具有独立数据空间,要进行数据传递只能通过通信方式进行,由于同一进程下线程间共享数据空间,所以一个线程的数据可以直接为其他线程直接所用,当然线程共享也带来其他问题,有的变量不能同时为两个线程修改。
#include<pthread.h>
1:线程有自己的ID的pthread_t数据类型,int pthread_equal(pthread_t tid1, pthread_t tid2),
2:获取自身线程的id,pthread_t pthread_self (void);返回线程id,在使用pthread_created(pthread_t *thread_id,NULL,void*(*fun)(void*), void* args)虽然第一个参数已经保存线程的id,但是,前提是主线程首先执行时才能实现,如果不是,那么thread指向未初始化的变量,那么子线程想使用必须调用pthead_self();
3:线程的创建 int pthread_create(pthread_t *thread_id,NULL,void*(*fun)(void*), void* args)参数:第一个参数为指向线程描述符的指针,第二个参数设置线程属性,第三个参数线程运行函数起始地址,最后一个参数运行函数的参数。新创建的线程由fun函数的地址开始运行,
4:线程的终止与等待:线程依进程而存在的,当进程终止时,线程也终止,当然也有在不终止整个进程的情况下终止线程,线程可以被同一进程的其他线程取消,退出时调用
void pthread_exit(void *rval_ptr)。
5:获得线程的终止状态,int pthread_join(pthread_t thread,void **rval_ptr)调用pthread_join进程将一直阻塞,直到线程调用pthread_exit()。
修改线程属性:
属性结构pthread_attr_t,属性主要包括:是否绑定、是否分离、堆栈地址、堆栈大小、优先级。默认属性:非绑定、非分离、缺省1M的堆栈、与父进程同级别优先级。
关于线程的绑定,牵扯到轻进程(LWP:light weight process)。轻进程可以理解为内核线程,它位于用户层和系统层之间,系统对线程资源的分配和对线程的控制都是通过轻进程来实现的,一个轻进程可以控制一个或多个线程。默认情况下,启动多少轻进程,哪些轻进程来控制哪些线程是由系统决定的,这种情况为非绑定。绑定情况即某个线程“绑”一个轻进程上。被绑定的线程具有较高的响应速度,因为CPU时间片的调度偏向轻进程。设置线程绑定的函数为pthread_attr_setcope,两个参数,第一个参数指向属性结构的指针,第二个参数绑定类型,下面代码创建一个绑定线程:
pthread_attr_init(&attr)
pthread_attr_setcope(&attr,PTHREAD_SCOPE_SYSTEM);
pthread_create(&tid, &attr, (void *)my_function , NULL);
线程的分离状态决定一个线程以什么样的状态结束自己,默认非分离状态,这种情况下原有的线程等待创建的线程结束,只有当pthread_join()函数返回时,创建的线程才算终止,释放占用的资源。而分离线程没有被其他线程等待,自己运行结束了,线程也就终止了,马上释放资源。设置分离现成的函数为
pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二个参数选为PTHREAD_CREATE_DETACHED(分离线程)和PTHREAD_CTREATE_JOINABLE(非分离线程),这里注意:如果设置一个线程为分离线程,而这个线程运行又非常快,它很有可能在pthread_create函数返回之前就终止,它终止后可能就把线程号和其他资源交给其他线程,这样调用pthread_create就得到一个错误线程号,这时就要采取同步措施,最简单的就是在线程里调用pthread_cond_timewait函数,让线程等待一会,留出时间让pthread_create返回,这里注意不要使用诸如wait之类的函数,它们会使整个进程睡眠,没有解决线程同步问题。
另一个属性是线程的优先级存放在sched_param中,用函数pthread_attr_getschedparam和函数pthread_attr_setschedparam进行存放,一般是先去优先级,对取得的值修改后再存放回去。
#include<pthread.h>
#include<sched.h>
pthread_attr_t attr ;
pthread_t tid ;
sched_param param;
int newprio = 20;
pthread_attr_init(&attr) ;
pthread_attr_getschedparam(&attr , ¶m );
param.sched_priority=newprio ;
pthread_attr_setschedparam(&attr , ¶m);
pthread_create(&tid , &attr , (void *)fun , arg) ;
线程数据处理:各个线程共享父进程沿袭的数据段,可以分别、获得修改数据,但是注意当有多个不同的线程访问相同的变量时。许多函数是不可重入的,即同时不能运行一个函数的多个拷贝,在函数中声明的静态变量作为函数返回值会带来问题,因为如果返回的是函数内部静态声明的地址,则在一个线程调用函数返回的地址后使用该地址指向的数据,别的线程可能此函数并修改这段数据。在线程中共享的变量用volatile定义,这是为了放置编译器优化时改变它们的使用方式。为了保护变量我们必须使用信号量、互斥的方法保证变量的正确使用,下面介绍线程数据处理的有关知识:
线程数据:在单线程的程序,有两种基本数据:全局变量和局部变量。但在多线程程序还有线程数据(TSD:thread specific data)。它和全局变量很像,在线程内部,各个函数都可以像调用全局变量一样调用它,但它对线程外部的其他线程不可见,比如我们常见的变量errno,它返回标准的出错信息,它显然不是一个局部变量,但几乎每个函数都要调用它,但它又不是全局变量,否则A线程输出的可能是B线程的出错信息,要实现诸如此类变量,就必须使用线程数据,我们为每个线程创建一个键,在每个线程都是使用这个键表示线程数据,但在不同线程里,这个键代表的数据是不同的,在同一线程代表同样的数据。
创建键的函数原型为: extern int pthread_key_create_P(pthread_key_t *_key , void (*__destr_function)(void *));第一个参数为指向一个键值指针,第二个参数指明一个destructor函数,如果第二个参数不为空,那么当每个线程结束时,系统将调用这个函数释放绑定在这个键上的内存块。这个函数常和函数pthread_once(pthread_once_t *once_control,void (*initrouttine)(void ))一起使用,为了让这个键值只被创建一次,函数pthread_once声明一个初始化函数,第一次调用pthread_once时调用这个函数。
应用如下:
pthread_key_t myWinkey ; //声明一个键
void createWindow(void ) {
FI_Window *win ;
static pthread_create_t once = PTHREAD_ONCE_INIT ;
pthread_once(&once , createMyKey) ;//调用create_MyKey,创建键
win = new FI_Window(0, 0, 100, 100, “MyWindow”) ;//创建一个新的window
setWindow(win) ;
pthread_setspecific(MyWinkey , win);//将窗口指针绑定在MyWinKey上
}
void freeWinKey(FI_Window *win) { //函数freeWinKey释放空间
delete win ;
}
这样,在不同的线程中调用函数createdMyWin,就可以得到在线程内部均可见的变量,这个变量通过pthread_getspecific得到,在上面的例子,我们使用 pthread_setspecific将线程数据和一个键绑定在一起,原型如下:
extern int pthread_setspecific_P(pthread_key_t __key , __const void * __pointer) ;
extern int pthread_getspecific_P(pthread_key_t __key) ;
用pthread_setspecific为一个键指定一个新的线程数据时,必须释放自己原有的线程数据以回收空间,这个过程函数通过pthread_key_delete删除一个键,这个键占用的内存将被释放,它只释放键占用的内存,并不释放该键关联的线程数据占用的内存单元,而且它也不会触发函数pthread_key_create中定义的destructor函数,线程数据的释放必须在释放键之前完成。
互斥锁:
用来保证在一段时间内只有一个线程执行一段代码,先看以下代码,这是一个读写程序,它们共用一个缓冲区,并且我们假定一个缓冲区只能保存一条信息,缓冲区有两个状态:有信息或没有信息。
void reader_function(void)
void write_function(void )
char buffer ;
int buffer_has_item = 0;
pthread_mutex_t mutex ;
struct timespec delay ;
void main (void) {
pthread_t reader ;
delay.tv_sec = 2 ; //定义延迟时间
delay.tv_nec = 0 ;
pthread_mutex_init(&mutex, NULL ) ;//用默认属性初始化一个互斥锁
pthread_create(&reader , pthread_attr_default , (void *)&read_function , NULL) ;
write_function ();
}
void write_function (void ) {
while(1) {
pthread_mutex_lock (&mutax) ;//锁定互斥锁
if(0 == buffer_has_item) {
buffer = make_new_item();
buffer_has_item = 1;
}
pthread_mutex_unlock(&mutex) ;
pthread_delay_np(&delay) ;
}
}
void reader_function (void) {
while(1) {
pthread_mutex_lock(&mutex) ;
if(1 == buffer_has_item) {
consume_item_buffer(buffer) ;
buffer_has_item = 1 ;
}
pthread_mutex_unlock(&mutex) ;
phtread_delay_np(&delay) ;
}
}
这里声明了互斥锁变量mutex,结构pthread_mutex_t为不公开数据类型,其中包含一个属性分配对象。函数pthread_mutex_init用来生成一个互斥锁。NULL表示使用默认属性,如果需要声明特定属性的互斥锁,要调用函数pthread_mutexattr_init,函数pthread_mutexattr_setpshared和函数pthread_mutexattr_settype用于设置互斥锁属性,前一个函数设置属性pshared,它有两个取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用于不同进程之间线程同步,后者用于同步本进程的不同线程,在上面的例子使用默认属性PTHREAD_PROCESS_PRIVATE。后一个函数用于设置互斥锁类型,可选的类型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD_MUTEX_DEFAULT。它们定义了不同的上锁和解锁机制。一般情况下,最后一个为默认属性。
pthread_mutex_lock声明开始用互斥锁上锁,此后的代码直至调用到pthread_mutex_unlock为止,均被上锁,即同一时间只能被一个线程调用执行。当一个线程调用到pthread_mutex_lock时,如果该锁此时被另一线程使用,那么此线程被阻塞,即程序将等待另一个线程释放此互斥锁。上例使用pthread_delay_np函数,让线程睡眠一段时间,就是为了防止一个线程始终占用此函数。
注意使用互斥锁的时候很有可能会出现死锁,两线程同时占用两个资源,并按不同顺序锁定相应互斥锁,例如两个线程都需要锁定互斥锁1和互斥锁2,a线程先锁定互斥锁1,b线程先锁定互斥锁2,这时就出现死锁,此时我们就使用函数pthread_mutex_trylock,它是pthread_mutex_lock的非阻塞版本,当它发现死锁不可避免它会返回相应的信息,然后做相应的处理。