Linux多线程编程

      线程是处理机调度的基本单位。使用多线程的理由之一是和进程相比,它是一种非常“节俭”的多任务操作方式。启动一个线程所花费的空间远远小于启动一个进程花费的空间,而且,线程间彼此切换所需的时间也远远少于进程间切换所需的时间。

     与进程相比,线程间的关系紧密得多。虽然各线程为保持自己的控制流而独有寄存器和堆栈,但由于两个线程从属于同一个进程,它们共享同一个地址空间,所以动态堆、静态数据区及程序代码为各线程共享。

    使用多线程的理由之二是线程间方便的通信机制。对于不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式既费时又不方便。而线程不然。

     与进程相比,多线程程序作为一种多任务、并发的工作方式,还有以下优点:

(1)提高应用程序响应,特别是对于图形界面程序尤其有意义;

(2)改善程序结构,一个既长又复杂的进程可以考虑分为多个线程,这样可以使程序利于理解和修改。

    Linux系统下的多线程实现

    Linux系统下的多线程遵循POSIX线程接口,称为PTHREAD。编写Linux下的多线程程序,需要头文件pthread.h,连接是需要库文件libpthread.a。PTHREAD库中有大量的API函数。下面介绍的主要是线程创建、挂起和退出的几个主要函数。

 (1)线程创建函数:pthread_create()

#include<pthread.h>
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
 
 

函数pthread_create用来创建一个线程,第一个参数为指向线程标志符的指针;第二个参数用来设置线程属性,NULL表示使用默认属性的线程;第三个参数是线程运行函数的起始地址;最后一个参数是运行函数的参数,NULL表示不需要参数。当创建线程成功时,函数返回0;若不为0,则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL,前者表示系统限制创建线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。

(2)线程等待函数:pthread_join()

#include<pthread.h>

int pthread_join(pthread_t th, void **thread_return);

pthread_join函数用来等待另一个线程的结束。该函数可以阻塞调用它的线程,直到被等待的线程结束,函数才返回,被等待的线程的资源就被回收。该函数类似于进程间的wait函数,用来同步终止并释放资源,而在一般情况下,线程退出之后,该线程所占用的资源并不会随着线程终止而得到释放。第一个参数是被等待的线程标志符,第二个参数是用户定义的一个用来存储被等待线程的返回值,该函数是一个线程阻塞函数。

(3)线程结束函数:pthread_exit()

#include<pthread.h>

void pthread_exit(void *retval);

线程结束有两种方式,一种是线程创建后,就开始运行相关的线程函数,函数结束了,调用它的线程就结束了;另一种是线程的主动行为,通过调用pthread_exit函数。需要注意的是,在使用线程函数时,不能随便调用exit退出函数进行错误处理,因为exit的作用是使调用进程终止。pthread_exit函数唯一的参数是函数的返回代码,只要pthread_join的第二个参数pthread_return不是NULL,这个值将被传递给thread_return,最后,要注意的是一个线程不能被多个线程等待否则第一个接收到信号的线程成功返回,其余的调用pthread_join的线程则返回错误代码ESRCH。

    修改线程属性

    线程的属性结构为pthread_attr_t,定义在头文件pthreadh.h中。属性值不能直接设置,需要使用相关函数进行操作,初始化函数是pthread_attr_init,这个函数必须在pthread_create函数之前调用。属性值包括是否绑定、是否分离、堆栈地址、堆栈大小和优先级。默认的属性是非绑定、非分离、缺省1MB的堆栈和与父进程同样级别的优先级。

(1)绑定属性

   系统对线程资源的分配和对线程的控制是通过轻进程来实现的,一个轻进程可以控制一个或者多个线程。默认情况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统控制的,这种状况即为非绑定的。绑定状况下,线程将被绑定在一个轻进程上。被绑定的线程具有较高的需要速度,因为CPU时间片的调度是面向轻进程的,被被绑定的线程可以保证在需要的时候它总有一个轻进程可以用。通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求。下面的代码即创建一个绑定的线程。

#include<pthread.h>

pthread_attr_t attr;

pthread_t tid;

pthread_attr_init(&attr);     /*初始化为属性值,均为默认属性值*/

pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); 

pthread_create(&tid, &attr, (void *)my_function, NULL);

注:设置线程绑定状态的函数为pthread_attr_setscope,第一个参数是向属性结构体的指针,第二个参数是绑定类型,它有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。

(2)分离属性

线程的分离状态决定一个线程以什么样的方式来终止自己。非分离状态的线程只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占有的资源。而分离线程不是这样的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。设置分离状态的线程的函数为:

pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和PTHREAD_CREATE_JOIUABLE(非分离线程)。但要注意的是,如果设置一个线程为分离线程,而这个线程又运行得非常快,它可能在pthread_create函数返回之前就终止了,导致调用pthread_create的线程得到错误的线程号。要避免这样的情况,可以采取一定的同步措施,最简单地方法之一是在被创建的线程里调用pthread_cond_timewait函数,让线程等一会儿,留出足够的时间让函数pthread_create返回。设置一段等待时间是多线程编程常用的方法。但是要注意不要使用诸如waite()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。

(3)优先级

线程优先级存放在结构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, &param);

param.sched_priority = newprio;

pthread_attr_setschedparam(&attr, &param);

pthread_create(&tid, &attr, (void *)myfunction, myarg);

  多线层访问控制

        线程同步可以使用互斥锁和信号量的方式来解决线程间数据的共享和通信问题。互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其他的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新检测条件是否满足。一般说来,条件变量被用来进行线程间的同步。以下是相关函数:

(1)条件变量初始化:pthread_cond_init()

int pthread_cond_init(pthread_cond_t *cond, __const pthread_condattr_t *cond_attr);

pthread_cond_init函数条件变量的结构为pthread_cond_t。该函数的第一个参数是指向pthread_cond_t的指,第二个参数是指向结构pthread_condattr_t的指针。结构pthread_condattr_t是条件变量的属性结构,与互斥锁一样是用来设置条件变量时进程内可用还是进程间可用,默认值是PTHREAD_PROCESS_PRIVATE,此条件变量被同一个进程内的各个线程使用。注意初始化条件变量只有未被使用时才能重新初始化或被释放。释放一个条件变量的函数为pthread_cond_destory(pthread_cond_t cond)。

(2)使线程阻塞在一个条件变量上的函数:pthread_cond_wait()

extrern int pthread_cond_wait(pthread_cond_t *__restrict__cond, pthread_mutex_t  *__restrict__mutex)

线程借卡mutex指向的锁并被条件变量cond阻塞,线程可以被函数pthread_cond_signal和函数pthread_cond_broadcast唤醒。但要注意的是,条件变量值是起阻塞和唤醒线程的作用,具体判断条件还需用户给出。例如一个变量是否为0等,线程被唤醒后,它将重新检查判断条件变量是否满足,如果还不满足,那么一般说来线程应该仍阻塞在这里,等待被下一次唤醒。这个过程一般用while语句实现。

(3)阻塞线程的函数:pthread_cond_timedwait()

extern int pthread_cond_timewait __p(pthread_cond_t *__cond pthread_mutex_t *__mutex, __const struct timespec **abstiem);

该函数比pthread_cond_wait多了一个时间参数,经历了abstime段时间后,即使条件变量不满足,阻塞也被解除。

(4)释放阻塞在条件变量上的线程:pthread_cond_signal()

extern int pthread_cond_signal(pthread_cond_t *__cond);

该函数用来释放阻塞在条件变量cond上的一个线程,多个线程阻塞在此条件变量上时,哪一个线程被唤醒,是由线程调度策略决定的。要注意的是,必须用保护条件变量的互斥锁来保护这个函数,否则条件满足信号有可能在测试条件和调用pthread_cond_wait函数之间被发出,从而造成无限制的等待。

 其他常用的多线程函数

(1)获取父进程ID

pthread_t pthread_self(void);

(2)测试两个线程号是否相同

int pthread_equal(pthread_t __thread1, pthread_t __thread2);

(3)互斥量初始化

pthread_mutex_init(pthread_mutex_t *, __const pthread_mutexattr_t *);

(4)再试一次获得对互斥量的锁定(非阻塞)

int pthread_mutex_trylock(pthread_mutex_t *__mutex);

(5)锁定互斥量(阻塞)

int pthread_mutex_lock(pthread_mutex_t *__mutex);

(6)解锁互斥量

int pthread_mutex_unlock(pthread_mutex_t *__mutex);

(7)条件变量初始化

int pthread_cond_init(pthread_cond_f *__restrict__cond, __const pthread_condattr_t *__restrict__cons_attr);

(8)销毁条件变量COND

int pthread_cond_destroy(pthread_cond_t *__cond);

(9)唤醒线程等待条件变量

int pthread_cond_signal(pthread_cond_t *__cond);

(10)等待条件变量(阻塞)

int pthread_cond_wait(pthread_cond_t *__restrict__cond, pthread_mutex_t *__restrict__mutex);

(11)在指定的时间到达前等待条件变量

int pthread_cond_timedwait(pthread_cond_t *__restrict__cond, pthread_mutex_t *__restrict__mutex, __const struct timespec *__restrict__abstime);












































你可能感兴趣的:(多线程,linux)