第一节:线程的概念

来源:互联网 发布:朝鲜族知乎 编辑:程序博客网 时间:2024/04/30 16:11

linux下写代码也有那么一定的时间了,线程的使用可以说一直贯穿其中,今天也算对以前的知识进行一番的总结吧!后面会慢慢的总结,今天先说一下概念性的东西。其实这个概念性的东西啊 就像市面上那些所谓的专家的吃饭法宝(忽悠的伎俩~~)!言归正传!

   进程是程序执行时的一个实例,是执行程序在一定数据集上运行的过程,LINUX系统分配资源的基本单位。 

   线程是进程的一个实体,CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),一个线程可以创建和撤销另一个线程

   进程是资源管理的最小单位,线程是程序执行的最小单位。

   用Posix thread进行多线程设计,就不怕跨平台了,因为很多OS都兼容Posix thread,Linux/Windows等,甚至嵌入式系统上(如rt-thread)都支持posix thread API。线程有比进程体积小,速率高,速度快等优势。所以编程时,如果涉及到效率和速度时,采用pthread_create()一个线程总比fork()一个进程好些。

    Posxi thread 线程操作主要有创建(creation),终止(termination),同步(joinsblocking),调度(scheduling),数据管理(datamanagement)和交互(interaction).

线程间共享:

.进程指令(process instructions)

.大部分数据(most data)

.文件(descriptors)

.信号和信号句柄(signals and signal handlers)

.当前工作目录(current working directory)

.用户和组id(user and group id)

线程独自属性:

.线程id(thread id)

.寄存器(内容)和栈不同(set of registers,stack pointer)

.局部变量,返回地址(stack for local variables,return addresses)

.信号mask(signal mask)

.优先级(priority)

.返回值(return value errno)

第一节:Linux线程函数

 1.创建线程:int pthread_create(pthread_t *tid,const pthread_attr_t *attr,void *(*start_routine)(void *),void *arg);

 2.线程属性初始化int pthread_attr_init(pthread_attr_t *attr);

 线程属性 pthread_attr_t结构如下:

          typedef struct

          {

                int                    detachstate;   线程的分离状态

                int                    schedpolicy;  线程调度策略

                struct sched_param       schedparam;  线程的调度参数

                int                    inheritsched;  线程的继承性

                int                    scope;       线程的作用域

                size_t                 guardsize;   线程栈末尾的警戒缓冲区大小

                int                    stackaddr_set;

                void *                 stackaddr;   线程栈的位置

                size_t                 stacksize;    线程栈的大小

           }pthread_attr_t;

    __detachstate,表示新线程是否与进程中其他线程脱离同步,如果置位则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源。缺省为PTHREAD_CREATE_JOINABLE状态。这个属性也可以在线程创建并运行以后用pthread_detach()来设置,而一旦设置为PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复到 PTHREAD_CREATE_JOINABLE状态。

    __schedpolicy,表示新线程的调度策略,主要包括SCHED_OTHER(正常、非实时)、SCHED_RR(实时、轮转法)和 SCHED_FIFO(实时、先入先出)三种,缺省为SCHED_OTHER,后两种调度策略仅对超级用户有效。运行时可以用过 pthread_setschedparam()来改变。

   __schedparam,一个struct sched_param结构,目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR SCHED_FIFO)时才有效,并可以在运行时通过pthread_setschedparam()函数来改变,缺省为0

    __inheritsched,有两种值可供选择:PTHREAD_EXPLICIT_SCHEDPTHREAD_INHERIT_SCHED,前者表示新线程使用显式指定调度策略和调度参数(即attr中的值),而后者表示继承调用者线程的值。缺省为PTHREAD_EXPLICIT_SCHED

   __scope,表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX的标准中定义了两个值: PTHREAD_SCOPE_SYSTEMPTHREAD_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU时间,后者表示仅与同进程中的线程竞争CPU。目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。

PS:这里还是要详细的对线程的属性进行一番的解析:

我们用pthread_create函数创建了一个线程,在这个线程中,我们使用了默认参数,即将该函数的第二个参数设为NULL。的确,对大多数程序来说,使用默认属性就够了,但我们还是有必要来了解一下线程的有关属性。属性结构为pthread_attr_t,它同样在头文件/usr/include/pthread.h中定义,喜欢追根问底的人可以自己去查看。属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。属性对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级。

默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级
  绑定牵涉到另外一个概念:轻进程(LWPLight Weight Process)。轻进程可以理解为内核线程,它位于用户层和系统层之间。系统对线程资源的分配、对线程的控制是通过轻进程来实现的,一个轻进程可以控制一个或多个线程。默认状况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为非绑定的。绑定状况下,则顾名思义,即某个线程固定的""在一个轻进程之上。被绑定的线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候它总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求。
  设置线程绑定状态的函数为pthread_attr_setscope,它有两个参数,第一个是指向属性结构的指针,第二个是绑定类型,它有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)PTHREAD_SCOPE_PROCESS(非绑定的)。下面的代码即创建了一个绑定的线程。
#include 
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_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。程序员应该根据自己的需要,选择适当的分离状态。设置线程分离状态的函数为pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二个参数可选为PTHREAD_CREATE_DETACHED)分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程)。这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。


 【优先级】另外一个可能常用的属性是线程的优先级,它存放在结构sched_param中。用函数pthread_attr_getschedparam和函数pthread_attr_setschedparam进行存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。下面即是一段简单的例子。
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 *)myfunction, myarg);
  

3.线程函数去初始化int pthread_attr_destroy(pthread_attr_t *attr);

4.结束线程:void pthread_exit(void *status);

5.等待线程结束:int pthread_join(pthread_t tid,void **status);

6.分离线程:int pthread_detach(pthread_t tid);

将非分离的线程设置为分离线程。即通知线程库在指定的线程终止时回收线程占用的内存等资源。在一个线程上使用多次pthread_detach的结果是不可预见的。

7.设置线程的属性(优先级和调用策略):int pthread_setschedparam(pthread_t target_thread,int policy,const sturct sched_param *param);

8.终止线程:int pthread_cancel(pthread_t thread);

9.使能cancel状态:int pthread_setcancelstate(int state,int *oldstate);

10.设置退出类型:int pthread_setcanceltype(int type,int *oldtype);

第二节:线程同步与互斥

 .互斥量

 1.初始化互斥锁int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restric attr);

pthread_mutex_init() 函数是以动态方式创建互斥锁的,参数attr指定了新建互斥锁的属性。如果参数attr为空,则使用默认的互斥锁属性,默认属性为快速互斥锁 。互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。

       互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。当前(glibc2.2.3,linuxthreads0.9)有四个值可供选择:

· PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。

· PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。

· PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。

· PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。

  2.初始化互斥锁属性对象int pthread_mutexattr_init(pthread_muexattr_t *mattr)

  3.互斥锁操作_加锁:int pthread_mutex_lock(pthread_mutex_t *mutex);  

//堵塞等待

                     int pthread_mutex_trylock(pthread_mutex_t *mutex);  //非堵塞锁

  4.互斥锁操作_解锁int pthread_mutex_unlock(pthread_mutex_t *mutex);

  5.销毁互斥锁:int pthread_mutex_destroy(pthread_mutex_t *mutex);

.读写锁

一次只有一个线程可以占有写模式的读写锁但是可以有多个线程同时占有读模式的读写锁.

· 当读写锁是写加锁状态时在这个锁被解锁之前所有试图对这个锁加锁的线程都会被阻塞.

· 当读写锁在读加锁状态时所有试图以读模式对它进行加锁的线程都可以得到访问权但是如果线程希望以写模式对此锁进行加锁它必须阻塞知道所有的线程释放锁.

· 通常当读写锁处于读模式锁住状态时如果有另外线程试图以写模式加锁读写锁通常会阻塞随后的读模式锁请求这样可以避免读模式锁长期占用而等待的写模式锁请求长期阻塞.

  1.初始化读写锁:int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);

         函数参数:rwlock :要初始化的读写锁变量。

                   attr    :要初始化的读写锁属性

         返回值:   成功返回0.

        typedef struct _pthread_rwlock_t{

            struct _pthread_fastlock __rw_lock; //快速锁

             int __rw_readers;               //读线程的数量

            _pthread_descr __rw_writer;      //写线程

           _pthread_descr __rw_read_waiting; //等待的读线程

           _pthread_descr __rw_write_waiting; //等待的写线程

           int __rw_kind;               

           int __rw_pshared;       // 读写锁的共享范围

          }pthread_rwlock_t;

       pthread_rwlockattr_t

typedef struct{

int __lockkind; //读写锁的类型

int __pshared; //读写锁的共享方式

}pthread_rwlockattr_t;

2.销毁读写锁:int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

函数参数:rwlock :要销毁的读写锁变量。在释放读写锁占用的内存之前需要先通过pthread_rwlock_destroy对读写锁进行清理工作释放由init分配的资源.

3.以阻塞方式获取读锁:int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

同一个线程可以获取读锁多次,但是必须保证调用相同次数的pthread_rwlock_unlock(),如果此线程已经拥有了写锁,那么结           果将是不确定的(依赖于具体的实现)

4.以非阻塞方式获取读锁:int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

5.以堵塞方式获取写锁:int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

            一个线程不能多次获取写锁,而且不能同时拥有读锁和写锁。

6.以非堵塞方式获取写锁:int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

7.释放读写锁:int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

 对于读锁,线程是可以多次或的的,所以释放的次数要和锁定的次数匹配.对于写锁,一个线程只能获得一次。

 .条件变量

        与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。

        条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。

       使用条件变量之前要先进行初始化。可以在单个语句中生成和初始化一个条件变量如:pthread_cond_t my_condition=PTHREAD_COND_INITIALIZER;(用于进程间线程的通信)。可以利用函数pthread_cond_init动态初始化。

条件变量本身由互斥量保护,所以在改变条件状态前必须锁住互斥量。

       条件变量分为两部分条件和变量条件本身是由互斥量保护的线程在改变条件状态前先要锁住互斥量它利用线程间共享的全局变量进行同步的一种机制。

1.初始化条件变量int pthread_cond_int(pthread_cond_t *cv,const pthread_condattr_t *cattr);

        函数参数:pthread_cond_t *cv:初始化的对象

                  pthread_condattr_t *cattr:条件变量属性

        当参数cattr为空指针时,函数创建的是一个缺省的条件变量。否则条件变量的属性将由cattr中的属性值来决定。调用pthread_cond_init函数时,参数cattr为空指针等价于cattr中的属性为缺省属性,只是前者不需要cattr所占用的内存开销。这个函数返回时,条件变量被存放在参数cv指向的内存中。不能由多个线程同时初始化一个条件变量。当需要重新初始化或释放一个条件变量时,应用程序必须保证这个条件变量未被使用。

2.阻塞等待条件:int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex)
      等待条件函数等待条件变为真传递给pthread_cond_wait的互斥量对条件进行保护调用者把锁住的互斥量传递给函数函数把调用线程放到等待条件的线程列表上然后对互斥量解锁这个操作是原子的这样便关闭了条件检查和线程进入休眠状态等待条件改变这个操作之间的时间通道这样线程就不会错过条件的任何变化.   pthread_cond_wait返回时互斥量再次被锁住.
3.超时等待:int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *retrict timeout);

      const struct timespec *restrictabstime);
4.通知条件:int pthread_cond_signal(pthread_cond_t *cv);

             该函数被用来释放被阻塞在指定条件变量上的一个线程。必须在互斥锁的保护下使用相应的条件变量。否则对条件变量的解锁有可能发生在锁定条件变量之前,从而造成死锁。唤醒阻塞在条件变量上的所有线程的顺序由调度策略决定,如果线程的调度策略是SCHED_OTHER类型的,系统将根据线程的优先级唤醒线程。如果没有线程被阻塞在条件变量上,那么调用pthread_cond_signal()将没有作用。

5.通知所有线程:int pthread_cond_broadcast(pthread_cond_t *cv);

          函数唤醒所有被pthread_cond_wait函数阻塞在某个条件变量上的线程,参数cv被用来指定这个条件变量。当没有线程阻塞在这个条件变量上时,pthread_cond_broadcast函数无效。由于pthread_cond_broadcast函数唤醒所有阻塞在某个条件变量上的线程,这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用pthread_cond_broadcast函数。

6.释放条件变量:int pthread_cond_destroy(pthread_cond_t *cv);

原创粉丝点击