linux 多线程编程

进程与线程

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

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

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

Linux线程函数

        1.创建线程:pthread_create()

      头文件:#include <pthread.h>

       函数原型:  int pthread_create(pthread_t *tid,const pthread_attr_t *attr,void *(*start_routine)(void *),void *arg);

       函数参数:  pthread_t *tid:存放线程ID的指针

                       pthread_attr_t *attr:设置线程属性的结构体,通常为NULL。

                       void*(*start_routine)(void *):线程执行函数。

                       void *arg:线程执行函数参数。

       返回值:   成功返回0.

        2.线程属性初始化:pthread_attr_init()

        pthread_attr_init 函数初始化线程属性对象attr,填充所有属性为默认值。这个函数必须在pthread_create函数之前调用,做为pthread_create 函数的第2个参数。属性对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。

         函数原型:int pthread_attr_init(pthread_attr_t *attr);

         函数参数: attr:线程属性变量。

         返回值:      成功返回0.

   线程属性 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_SCHED和PTHREAD_INHERIT_SCHED,前者表示新线程使用显式指定调度策略和调度参数(即attr中的值),而后者表示继承调用者线程的值。缺省为PTHREAD_EXPLICIT_SCHED。

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


        3.线程函数去初始化:pthread_attr_destroy()

          如果要去除对pthread_attr_t结构的初始化,可以调用pthread_attr_destroy函数。如果pthread_attr_init实现时为属性对象分配了动态内存空间,pthread_attr_destroy还会用无效的值初始化属性对象,因此如果经pthread_attr_destroy去除初始化之后的pthread_attr_t结构被pthread_create函数调用,将会导致其返回错误。

         函数原型:int pthread_attr_destroy(pthread_attr_t *attr);

         函数参数:attr:线程属性变量。

         返回值:    成功返回0.


         4.结束线程:pthread_exit()

         函数原型:void pthread_exit(void *status);

         函数参数:void *status:线程结束后的返回值。

pthread_exit()与return

             理论上说,pthread_exit()和线程宿体函数退出的功能是相同的,函数结束时会在内部自动调用pthread_exit()来清理线程相关的资源。但实际上二者由于编译器的处理有很大的不同。在进程主函数(main())中调用pthread_exit(),只会使主函数所在的线程(可以说是进程的主线程)退出;而如果是return,编译器将使其调用进程退出的代码(如_exit()),从而导致进程及其所有线程结束运行。其次,在线程宿主函数中主动调用return,如果return语句包含在pthread_cleanup_push()/pthread_cleanup_pop()对中,则不会引起清理函数的执行,反而会导致segment fault。


         5.等待线程结束:pthread_join()

          函数原型:int pthread_join(pthread_t tid,void **status);

          函数参数:pthread_t tid:要等待结束的线程ID(当前进程中非分离的线程)。void **status用于存放线程的退出状态。

          返回值:    成功返回0.


         6.分离线程:pthread_detach()

          函数原型:int pthread_detach(pthread_t tid);

          函数参数:pthread_t tid需要设置的线程ID。

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

         7.设置线程的属性(优先级和调用策略):pthread_setschedparam()

          函数原型: int pthread_setschedparam(pthread_t target_thread,int policy,const sturct sched_param *param);

          函数参数: target_thread :线程ID。

                             policy:设置确定哪种线程调度策略:SCHED_OTHER(默认)、SCHED_RR、SCHED_FIFO

                             param:是struct sched_param类型指针,它包含成员变量sched_priority,指明要设置的线程优先级。                           

           得到线程属性参数用 pthread_attr_getschedparam(pthread_t target_thread,int policy,sturct sched_param *param);

         三种调度策略:

        SCHED_OTHER

         它是默认的线程分时调度策略,所有的线程的优先级别都是0,线程的调度是通过分时来完成的。简单地说,如果系统使用这种调度策略,程序将无法设置线程的优先级。请注意,这种调度策略也是抢占式的,当高优先级的线程准备运行的时候,当前线程将被抢占并进入等待队列。这种调度策略仅仅决定线程在可运行线程队列中的具有相同优先级的线程的运行次序。

       SCHED_FIFO

        它是一种实时的先进先出调用策略,且只能在超级用户下运行。这种调用策略仅仅被使用于优先级大于0的线程。它意味着,使用SCHED_FIFO的可运行线程将一直抢占使用SCHED_OTHER的运行线程J。此外SCHED_FIFO是一个非分时的简单调度策略,当一个线程变成可运行状态,它将被追加到对应优先级队列的尾部((POSIX 1003.1)。当所有高优先级的线程终止或者阻塞时,它将被运行。对于相同优先级别的线程,按照简单的先进先运行的规则运行。我们考虑一种很坏的情况,如果有若干相同优先级的线程等待执行,然而最早执行的线程无终止或者阻塞动作,那么其他线程是无法执行的,除非当前线程调用如pthread_yield之类的函数,所以在使用SCHED_FIFO的时候要小心处理相同级别线程的动作。

       SCHED_RR

       鉴于SCHED_FIFO调度策略的一些缺点,SCHED_RR对SCHED_FIFO做出了一些增强功能。从实质上看,它还是SCHED_FIFO调用策略。它使用最大运行时间来限制当前进程的运行,当运行时间大于等于最大运行时间的时候,当前线程将被切换并放置于相同优先级队列的最后。这样做的好处是其他具有相同级别的线程能在“自私“线程下执行。


         8.终止线程:pthread_cancel()

          函数原型:int pthread_cancel(pthread_t thread);

          函数参数:pthread_t thread:要终止的线程ID。

          返回值:    成功返回0.


        9.使能cancel状态:pthread_setcancelstate()

           允许/禁止线程被pthread_cancel终止.

          函数原型:int pthread_setcancelstate(int state,int *oldstate);

          函数参数: int state:要设置的新的状态,

                        PTHREAD_CANCEL_ENABLE 可以被终止的(默认),

                        PTHREAD_CANCEL_DISABLE 不可被终止的,

               int *oldstate:用于保存线程以前的状态。

       返回值:成功返回0.


       10.设置退出类型:pthread_setcanceltype()

        函数原型:int pthread_setcanceltype(int type,int *oldtype);

            函数参数:int type:要设置的类型。

                PTHREAD_CANCEL_DEFERRED 延迟终止请求,通过pthread_testcancel()设置取消点。

                PTHREAD_CANCEL_ASYNCHRONOUS 可以在任何时间被终止,

                           oldtype:保存以前的类型。

          返回值:成功返回0.


线程同步与互斥

           线程的同步, 发生在多个线程共享相同内存的时候, 这时要保证每个线程在每个时刻看到的共享数据是一致的。

线程同步有三种常用的机制: 互斥量(mutex), 读写锁(rwlock)和条件变量(cond).


    一.互斥量

           互斥量从本质上说就是一把锁, 提供对共享资源的保护访问.

        1.初始化互斥锁

         函数原型:int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restric attr);

         函数参数:mutex:要初始化的互斥锁指针。attr:指向属性对象的指针,该属性对象定义要初始化的互斥锁的属性。

        返回值: 成功返回0.

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

此外,还可以用宏 PTHREAD_MUTEX_INITIALIZER 来初始化静态分配的互斥锁,如下:

                                        pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

       互斥锁的属性在创建锁的时候指定,在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)

        函数参数:mattr:取值PTHREAD_PROCESS_PRIVATE(缺省)

                                                  PTHREAD_PROCESS_SHARED

        返回值:成功返回0.


        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.初始化读写锁:pthread_rwlock_init()

         函数原型: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.销毁读写锁:pthread_rwlock_destroy()

函数原型:int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

函数参数:rwlock :要销毁的读写锁变量。

                  在释放读写锁占用的内存之前, 需要先通过pthread_rwlock_destroy对读写锁进行清理工作, 释放由init分配的资源.


3.以阻塞方式获取读锁:pthread_rwlock_rdlock()

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

函数原型:int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);


4.以非阻塞方式获取读锁:pthread_rwlock_tryrdlock()

函数原型:int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);


5.以堵塞方式获取写锁:pthread_rwlock_wrlock()

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

函数原型:int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);


6.以非堵塞方式获取写锁:pthread_rwlock_trywrlock()

函数原型:int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

7.释放读写锁:pthread_rwlock_unlock()

         对于读锁,线程是可以多次或的的,所以释放的次数要和锁定的次数匹配.

         对于写锁,一个线程只能获得一次。

函数原型:int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);


     三.条件变量

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

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

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

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

        1.初始化条件变量

        头文件:#include <pthread.h>

        函数原型: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指向的内存中。不能由多个线程同时初始化一个条件变量。当需要重新初始化或释放一个条件变量时,应用程序必须保证这个条件变量未被使用。

            还可以用宏初始化:     pthread_cond_t cond=PTHREAD_COND_INITIALIER;属性置为NULL  


        2.阻塞等待条件:pthread_cond_wait()

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

       3.超时等待:pthread_cond_timewait()
        函数原型:int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *retrict timeout);
        
       4.通知条件:pthread_cond_signal()

        函数原型:int pthread_cond_signal(pthread_cond_t *cv);
        

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

       5.通知所有线程:pthread_cond_broadcast()

         函数原型:int pthread_cond_broadcast(pthread_cond_t *cv);

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

        6.释放条件变量:pthread_cond_destroy()

         函数原型:int pthread_cond_destroy(pthread_cond_t *cv);

        
Solaris 库(lib 线程)Linux POSIX 库(libp 线程)操作
sema_destroy()sem_destroy()销毁信号状态。
sema_init()sem_init()初始化信号。
sema_post()sem_post()增加信号。
sema_wait()sem_wait()阻止信号计数。
sema_trywait()sem_trywait()减少信号计数。
mutex_destroy()pthread_mutex_destroy()销毁或禁用与互斥对象相关的状态。
mutex_init()pthread_mutex_init()初始化互斥变量。
mutex_lock()pthread_mutex_lock()锁定互斥对象和块,直到互斥对象被释放。
mutex_unlock()pthread_mutex_unlock()释放互斥对象。
cond_broadcast()pthread_cond_broadcast()解除对等待条件变量的所有线程的阻塞。
cond_destroy()pthread_cond_destroy()销毁与条件变量相关的任何状态。
cond_init()pthread_cond_init()初始化条件变量。
cond_signal()pthread_cond_signal()解除等待条件变量的下一个线程的阻塞。
cond_wait()pthread_cond_wait()阻止条件变量,并在最后释放它。
rwlock_init()pthread_rwlock_init()初始化读/写锁。
rwlock_destroy()pthread_rwlock_destroy()锁定读/写锁。
rw_rdlock()pthread_rwlock_rdlock()读取读/写锁上的锁。
rw_wrlock()pthread_rwlock_wrlock()写读/写锁上的锁。
rw_unlock()pthread_rwlock_unlock()解除读/写锁。
rw_tryrdlock()pthread_rwlock_tryrdlock()读取非阻塞读/写锁上的锁。
rw_trywrlock()pthread_rwlock_trywrlock()写非阻塞读/写锁上的锁。


  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值