linux线程基础

参考了http://www.cnblogs.com/feisky/archive/2009/11/12/1601824.html,谢谢

一、线程介绍

线程是计算机中独立运行的最小单位,运行时占用很少的系统资源。可以把线程看成是操作系统分配CPU时间的基本单元。一个进程可以拥有一个至多个线程。它线程在进程内部共享地址空间、打开的文件描述符等资源。同时线程也有其私有的数据信息,包括:线程号、寄存器(程序计数器和堆栈指针)、堆栈、信号掩码、优先级、线程私有存储空间。

为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?
使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。

使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。

除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:
1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

二、线程相关的函数

1、线程属性

1 /* man pthread_attr_init */
2 typedef struct
3 {
4   int                   detachstate;    //是否与其他线程脱离同步
5   int                   schedpolicy;    //新线程的调度策略
6   struct sched_param   schedparam;        //运行优先级等
7   int                   inheritsched;    //是否继承调用者线程的值
8   int                   scope;            //线程竞争CPU的范围(优先级的范围)
9  size_t               guardsize;        //警戒堆栈的大小
10   int                   stackaddr_set;    //堆栈地址集
11   void *               stackaddr;        //堆栈地址
12  size_t               stacksize;        //堆栈大小
13 } pthread_attr_t;
属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。

2、创建线程

 int pthread_create(pthread_t *thread,pthread_attr_t *attr,void *(*start_routine)(void *),void *arg);

linux系统支持POSIX多线程接口,称为pthread。编写linux下的多线程程序,需要包含头文件pthread.h,链接时需要使用库libpthread.a

如果在主线程里面创建线程,程序就会在创建线程的地方产生分支,变成两个部分执行。线程的创建通过函数pthread_create来完成。成功返回0

参数:

thread: 参数是一个指针,当线程成功创建时,返回创建线程ID。

attr: 用于指定线程的属性

start_routine:该参数是一个函数指针,指向线程创建后要调用的函数。

arg: 传递给线程函数的参数。

pthread_tpthread_self(void);

函数作用:获得线程自身的ID。pthread_t的类型为unsigned longint,所以在打印的时候要使用%lu方式,否则显示结果出问题。

3、线程终止

两种方式终止线程。

第一通过return从线程函数返回,

第二种通过调用pthread_exit()函数使线程退出。

需要注意的地方:一是,主线程中如果从main函数返回或是调用了exit函数退出主线程,则整个进程终止,此时所有的其他线程也将终止。另一种是,如果主线程调用pthread_exit函数,则仅仅是主线程消亡,进程不会结束,其他线程也不会结束,知道所有的线程都结束时,进程才结束。

4、线程等待——正确处理线程终止

1 #include<pthread.h>
2 void pthread_exit(void *retval);// 这是线程的主动行为
3 void pthread_join(pthread_t th,void*thread_return);     

描述pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。

参数thread:线程标识符,即线程ID,标识唯一线程。retval:用户定义的指针,用来存储被等待线程的返回值。

返回值 0代表成功。失败,返回的则是错误号。

4 int pthread_detach(pthread_t th);

线程只能被一个线程等待终止(第一个能正常返回),并且应处于join状态(非DETACHED)。

在 Linux 平台下,当处理线程结束时需要注意的一个问题就是如何让一个线程善始善终,让其所占资源得到正确释放。在 Linux 平台默认情况下,虽然各个线程之间是相互独立的,一个线程的终止不会去通知或影响其他的线程。但是已经终止的线程的资源并不会随着线程的终止而得到释放,我们需要调用 pthread_join() 来获得另一个线程的终止状态并且释放该线程所占的资源。

如果你压根儿不关心一个线程的结束状态,那么也可以将一个线程设置为 detached 状态,从而来让操作系统在该线程结束时来回收它所占的资源。将一个线程设置为 detached 状态可以通过两种方式来实现。一种是调用 pthread_detach() 函数,可以将线程 th 设置为detached 状态。另一种方法是在创建线程时就将它设置为 detached 状态,首先初始化一个线程属性变量,然后将其设置为 detached 状态,最后将它作为参数传入线程创建函数 pthread_create(),这样所创建出来的线程就直接处于 detached 状态。

5、线程私有数据

进程内的所有线程共享进程的数据空间,因此全局变量为所有线程所共有。但有时线程也需要保存自己的私有数据,这时可以创建线程私有数据(Thread-specific Date)TSD来解决。在线程内部,私有数据可以被各个函数访问,但对其他线程是屏蔽的。例如我们常见的变量errno,它返回标准的出错信息。它显然不能是一个局部变量,几乎每个函数都应该可以调用它;但它又不能是一个全局变量,否则在A线程里输出的很可能是B线程的出错信息。要实现诸如此类的变量,我们就必须使用线程数据。我们为每个线程数据创建一个键,它和这个键相关联,在各个线程里,都使用这个键来指代线程数据,但在不同的线程里,这个键代表的数据是不同的,在同一个线程里,它代表同样的数据内容。

线程私有数据采用了一键多值的技术,即一个键对应多个数值,访问数据时好像是对同一个变量进行访问,但其实是在访问不同的数据。

创建私有数据的函数有4个:pthread_key_create(创建), pthread_setspecific(设置), pthread_getspecific(获取), pthread_key_delete(删除)。

1 #include <pthread.h>
2 int pthread_key_creadte(pthread_key_t *key,void (*destr_fuction) (void *));
3 int pthread_setspecific(pthread_key_t key,const void * pointer));
4 void * pthread_getspecific(pthread_key_tkey);
5 int pthread_key_delete(ptherad_key_t key);

具体用法如下:

1.创建一个类型为pthread_key_t类型的变量。

2.调用pthread_key_create()来创建该变量。该函数有两个参数,第一个参数就是上面声明的pthread_key_t变量,第二个参数是一个清理函数,用来在线程释放该线程存储的时候被调用。该函数指针可以设成NULL,这样系统将调用默认的清理函数。

3.当线程中需要存储特殊值的时候,可以调用pthread_setspcific()。该函数有两个参数,第一个为前面声明的pthread_key_t变量,第二个为void*变量,这样你可以存储任何类型的值。

4.如果需要取出所存储的值,调用pthread_getspecific()。该函数的参数为前面提到的pthread_key_t变量,该函数返回void *类型的值。

6、线程同步

线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点。linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量和异步信号。

1)互斥锁(mutex)

通过锁机制实现线程间的同步。同一时刻只允许一个线程执行一个关键部分的代码。

1 int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutex_attr_t *mutexattr);
2 int pthread_mutex_lock(pthread_mutex *mutex);
3 int pthread_mutex_destroy(pthread_mutex *mutex);
4 int pthread_mutex_unlock(pthread_mutex *

(1)先初始化锁init()或静态赋值pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIER

      PTHREAD_MUTEX_TIMED_NP:其余线程等待队列

      PTHREAD_MUTEX_RECURSIVE_NP:嵌套锁,允许线程多次加锁,不同线程,解锁后重新竞争

      PTHREAD_MUTEX_ERRORCHECK_NP:检错,与一同,线程请求已用锁,返回EDEADLK;

      PTHREAD_MUTEX_ADAPTIVE_NP:适应锁,解锁后重新竞争

(2)加锁,lock,trylock,lock阻塞等待锁,trylock立即返回EBUSY

(3)解锁,unlock需满足是加锁状态,且由加锁线程解锁

(4)清除锁,destroy(此时锁必需unlock,否则返回EBUSY,//Linux下互斥锁不占用资源内存

2)条件变量(cond)

利用线程间共享的全局变量进行同步的一种机制。

1 int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);     
2 int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
3 int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
4 int pthread_cond_destroy(pthread_cond_t *cond);  
5 int pthread_cond_signal(pthread_cond_t *cond);
6 int pthread_cond_broadcast(pthread_cond_t *cond);  //解除所有线程的阻塞

(1)初始化.init()或者pthread_cond_tcond=PTHREAD_COND_INITIALIER;属性置为NULL

(2)等待条件成立.pthread_wait,pthread_timewait.wait()释放锁,并阻塞等待条件变量为真

timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait)

(3)激活条件变量:pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程)

(4)清除条件变量:destroy;无线程等待,否则返回EBUSY

对于intpthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
intpthread_cond_timedwait(pthread_cond_t *cond
pthread_mutex_t *
mutex, const struct timespec *abstime);
一定要在mutex的锁定区域内使用。

如果要正确的使用pthread_mutex_lock与pthread_mutex_unlock,请参考
pthread_cleanup_push和pthread_cleanup_pop宏,它能够在线程被cancel的时候正确的释放mutex!

另外,posix1标准说,pthread_cond_signal与pthread_cond_broadcast无需考虑调用线程是否是mutex的拥有者,也就是所,可以在lock与unlock以外的区域调用。如果我们对调用行为不关心,那么请在lock区域之外调用吧。

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;/*初始化互斥锁*/
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;/*初始化条件变量*/
void *thread1(void *);
void *thread2(void *);
int i=1;
int main(void)
{
  pthread_t t_a;
  pthread_t t_b;
  pthread_create(&t_a,NULL,thread1,(void *)NULL);/*创建线程t_a*/
  pthread_create(&t_b,NULL,thread2,(void *)NULL); /*创建线程t_b*/
  pthread_join(t_a, NULL);/*等待线程t_a结束*/
  pthread_join(t_b, NULL);/*等待线程t_b结束*/
  pthread_mutex_destroy(&mutex);
  pthread_cond_destroy(&cond);
  exit(0);
}
void *thread1(void *junk)
{
  for(i=1;i<=6;i++)
  {
    printf("thread1 i=%d\n",i);
    pthread_mutex_lock(&mutex);/*锁住互斥量*/
    printf("thread1: lock %d\n", __LINE__);
    if(i%3==0){
      printf("thread1:signal 1 %d\n", __LINE__);
      pthread_cond_signal(&cond);/*条件改变,发送信号,通知t_b线程*/
      printf("thread1:signal 2 %d\n", __LINE__);
      sleep(1);
    }
    pthread_mutex_unlock(&mutex);/*解锁互斥量*/
    printf("thread1: unlock %d\n\n", __LINE__);
    sleep(1);
  }
}
void *thread2(void *junk)
{
  while(i<6)
  {
    printf("thread2 i=%d\n",i);
    pthread_mutex_lock(&mutex);
    printf("thread2: lock %d\n", __LINE__);
    if(i%3!=0){
      printf("thread2: wait 1 %d\n", __LINE__);
      pthread_cond_wait(&cond,&mutex);/*解锁mutex,并等待cond改变*/
      printf("thread2: wait 2 %d\n", __LINE__);
    }
    pthread_mutex_unlock(&mutex);
    printf("thread2: unlock %d\n\n", __LINE__);
    sleep(1);
  }
}


3)信号量

如同进程一样,线程也可以通过信号量来实现通信,虽然是轻量级的。

信号量函数的名字都以"sem_"打头。线程使用的基本信号量函数有四个。
#include <semaphore.h>
int sem_init (sem_t *sem , int pshared, unsigned int value);
这是对由sem指定的信号量进行初始化,设置好它的共享选项(linux只支持为0,即表示它是当前进程的局部信号量),然后给它一个初始值VALUE。

两个原子操作函数:
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
这两个函数都要用一个由sem_init调用初始化的信号量对象的指针做参数。
sem_post:给信号量的值加1;
sem_wait:给信号量减1;对一个值为0的信号量调用sem_wait,这个函数将会等待直到有其它线程使它不再是0为止。

intsem_destroy(sem_t *sem);
这个函数的作用是再我们用完信号量后都它进行清理。归还自己占有的一切资源。

三、进程和线程的关系

(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。

(2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。

(3)线程在执行过程中需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。

(4)cpu分给线程,即真正在cpu上运行的是线程。

(5)线程是指进程内的一个执行单元,也是进程内的可调度实体。

(6)不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行。

四、线程与进程的区别

(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。

(2)资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。统在运行的时候会为每个进程分配不同的内存区域,但是不会为线程分配内存(线程所使用的资源是它所属的进程的资源),线程组只能共享资源。这里线程与进程的控制表PCB相似,线程也有自己的控制表TCB,但是TCB中所保存的线程状态比PCB表中少多了。

(3)通信:对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。而一个线程的数据可以直接为其他线程所用,这不仅快捷,而且方便。

(4)系统开销:在创建或撤销进程的时候,由于系统都要为之分配和回收资源,导致系统的明显大于创建或撤销线程时的开销。但进程有独立的地址空间,进程崩溃后,在保护模式下不会对其他的进程产生影响,而线程只是一个进程中的不同的执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但是在进程切换时,耗费的资源较大,效率要差

(5)其他:线程是进程的一部分, 一个没有线程的进程是可以被看作单线程的,如果一个进程内拥有多个进程,进程的执行过程不是一条线(线程)的,而是多条线(线程)共同完成的。

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

进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。


猜你喜欢

转载自blog.csdn.net/E_ROAD_BY_U/article/details/55823467
今日推荐