Linux C++ 多线程

一 :Linux的多线程自内核 2.6开始,Linux才真正提供内核级别的线程支持。新的线程库常用的为NPTL,且更符合POSIX规范。

     具体包括:1、 创建线程、结束线程

                        2、读取、设置线程属性

                         3、POSIX线程同步方式:POSIX信号量、互斥锁、条件变量

    1.1  pthread_create  (头文件 pthread.h)、创建线程

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

thread 参数是新线程的标识符,即拿来对线程进行 引用。pthread_t 是一个整型类型。

attr参数用于设置线程的属性,传递NULL表示默认线程属性。(pthread_attr_t)结构体定义完整的线程属性。

start_routine、arg 参数分别指定线程 将要运行的函数及其参数。 

     1.2.1  pthread_exit     结束线程

void pthread_exit(void *retval);

    在创建的线程 运行完 start_routine函数、结束火最好调用此函数来 确保安全、干净的退出。 

    pthread_exit 函数 通过retval 参数向线程的回收者传递其退出信息。 它执行完之后不会返回到调用者, 而且永远不会失败。

     1.2.2 pthread_join   汇聚、等待结束

int pthread_join(pthread_t thread, void **retval);

  一个进程中的所有线程都可以调用 pthread_join 函数来回收其他线程(前提是目标线程是可回收的), 来等待其他线程结束。

    参数 thread 为线程标识符、retval为线程退出信息:成功返回0、失败返回错误码。

    1.2.3 pthread_cancel   撤销、取消(请求)

int pthread_cancel(pthread_t thread);

     thread参数为线程的一个 标识符、函数成功返回0失败返回错误码。

当接收到取消请求时、目标线程可以决定是否允许被取消、以及怎么取消。

int pthread_ setcancelstate( int state, int* oldstate); 
int pthread_ setcanceltype( int type, int* oldtype); 

第一个参数 分别用于设置 线程(是否允许取消)、(如何取消)

第二个参数 分别记录线程原来的取消状态、类型。

state 和 type都有两个可选值

 

    2  线程属性

    在创建线程时、pthread_attr_t 结构体定义了完整的线程属性。同时 线程库定义了一系列函数来操作 此结构体类型的变量。

来设置属性:选取其中常用两个如下:

/*初始化线程属性对象*/
int pthread_ attr_ init( pthread_ attr_ t* attr); 

/*销毁线程属性对象,被销毁的线程属性对象只有再次初始化之后才能继续使用*/ 

int pthread_ attr_ destroy( pthread_ attr_ t* attr);

    3 POSIX线程同步

     当对线程创建、销毁、定义属性  后我们需要考虑的一个问题便是 线程的一个同步问题。

我们讨论    3种专门用于线程同步的机制: POSIX 信号量、互斥 量和条件变量。

     3.1 信号量

      在Linux中信号量有两组 API、一是 System V IPC 信号量,二是 POSIX信号量。两组接口很相似、但不能说 一定可以互换。

      POSIX信号量函数名字 以 sem_开头、常用的信号量函数如下:

int sem_ init( sem_ t* sem, int pshared, unsigned int value); 
int sem_ destroy( sem_ t* sem); 
int sem_ wait( sem_ t* sem); 
int sem_ trywait( sem_ t* sem); 
int sem_ post( sem_ t* sem);

第一个参数 sem 指向被操作的信号量

sem_init 函数用于初始化一个未命名的信号量。 pshared 参数指定信号量的类型。 如果其值为0,表示信号量是当前进程的局部 信号量, 否则该信号量就可以在多个进程之间共享。 value 参数指定信号量初始值。 此外,初始化一个已经被初始化的信号量将 导致不可预期的结果。

sem_destroy 函数用于销毁信号量,以释放其占用的内核资源。如果销毁一个正被其他线程等待的信号量,则将导致不可预期的 结果。

sem_wait 函数以原子操作的方式将信号量的值减 1。如果信号量的值为 0,则sem_wait 将被阻塞,直到这个信号量具有非0 值。

sem_trywait 与 sem_wait 函数相似, 不过它始终立即返回, 而不论被操作的信号量是否具有非 0 值, 相当于 sem_ wait 的非阻塞版本。当信号量的值非0时,sem_trywait 对信号量执行减1操作。当信号量的值为 0 时,它将返回- 1 并设置errno为 EAGAIN。

sem_post 函数以原子操做的方式将信号量的值加 1。 当信号量的值大于 0 时,其他正在调用 sem_wait 等待信号量的 线程将被唤醒。

上面这些函数成功时返回 0, 失败则返回- 1并设置 errno。

.      3.2  互斥锁

  互斥锁、互斥量 可以用于保护关键代码段,确保独占式访问。

        在进入关键代码时,获得互斥锁并 将其加锁、离开时 则对其解锁以唤醒其他等待该互斥锁的线程。

int pthread_ mutex_ init( pthread_ mutex_ t* mutex
                        , const pthread_ mutexattr_ t* mutexattr);
int pthread_ mutex_ destroy( pthread_ mutex_ t* mutex);
int pthread_ mutex_ lock( pthread_ mutex_ t* mutex);
int pthread_ mutex_ trylock( pthread_ mutex_ t* mutex); 
int pthread_ mutex_ unlock( pthread_ mutex_ t* mutex);

第一个参数 mutex指向要操作 的目标互斥锁。 互斥锁的类型结构体  是 pthread_mutex_t

pthread_mutexattr_t 结构体定义一套完整的互斥锁属性。

    其函数作用 也与上差不多、当然也会有一些区别。mutexattr 参数用来指定互斥锁的属性。

pthread_mutex_init 函数用于初始化互斥锁。 mutexattr 参数指定互斥锁的属性。 如果将它设置为NULL,则表示使用默认属性。我们将在下 一 小节讨论互斥锁的属性。除了这个函数外,我们 还可以使用如下方式来初始化一个互斥 锁: pthread_mutex_t mutex= PTHREAD_MUTEX_INITIALIZER; 宏 PTHREAD_MUTEX_INITIALIZER实际上只是把互斥锁的各个字段都初始化为 0。

pthread_mutex_destroy函数用于销毁互斥锁,以释放其占用的内核资源。销毁一个 已经加锁的互斥 锁将导致不可预期的后果。

pthread_mutex_lock 函数以原子操作的方式给一个互斥锁加锁。 如果目标互斥锁已经被锁上, 则 pthread_mutex_lock 调用将阻塞,直到该互斥锁的占有者将其解 锁。

pthread_mutex_trylock 与 pthread_mutex_lock 函数 类似, 不过它始终立即返回,而不论被操作的互斥锁是否已经被加锁,相当于 pthread_mutex_lock 的非阻塞版本。当目标互斥锁未被加锁时,pthread_mutex_trylock 对互斥锁执行加锁操作。当互斥锁已经被 加锁时,pthread_mutex_trylock 将返回错误码 EBUSY。 需要注意的是,这里讨论的pthread_mutex_lock 和 pthread_mutex_trylock 的行为是针对普通锁而言的。后面我们将看到,对于其他类型的锁而言, 这两个加锁函数会有不同的行为。

pthread_mutex_unlock函数以原子操作的方式给一个互斥锁解锁。如果此时有其他线程正在等待这个互斥锁,则这些线程中的某一个将获得它。

上面这些函数成功时返回 0,失败则返回错误码。

对于pthread_mutexattr_t 结构体。 线程库提供了一系列函数来操作pthread_mutexattr_t 类型的变量,以方便我们获取和设置互斥锁属性。

 

      使用互斥锁的一个噩耗是死锁。死锁使得一个或多个线程被挂起而无法继续执行,而且这种情况还不容易被发现。前文提到,在一个线程中对一个已经加锁的普通锁再次加锁,将导致死锁。 这种情况可能出现在设计得不够仔细 递归函数中。另外,如果两个线程 按照不同的顺序来申请两个互斥锁,也容易产生死锁,

    3.3  条件变量

如果说互斥锁是用于同步线程对共享数据的访问的话, 那么条件变量则是用于在线程之间同步共享数据的值。 条件变量提供了一种 线程间的通知机制: 当某个共享数据达到某个值的时候,唤醒等待这个共享数据的线程。

int pthread_cond_init(pthread_cond_t* cond, const pthread_condattr_t* cond_attr);
int pthread_cond_destroy( pthread_cond_t* cond);
int pthread_cond_broadcast( pthread_cond_t* cond); 
int pthread_cond_signal( pthread_cond_t* cond); 
int pthread_cond_wait( pthread_cond_t* cond, pthread_mutex_t* mutex);

 

这些函数的第一个参数 cond指向要操作的目标条件变量, 条件变量的类型是 pthread_cond_t 结构体。

pthread_cond_init 函数用于初始化条件变量。 cond_attr 参数指定条件变量的属性。如果将它设置 为 NULL, 则表示使用默认属性。条件变量的属性不多,而且和互斥锁的 属性类型相似,所以我们不再赘述。 除了pthread_cond_init 函数,我们还可以使用如下方式来初始化一个条件变量: pthread_ cond_ t cond= PTHREAD_ COND_ INITIALIZER; 宏 PTHREAD_COND_INITIALIZER 实际上只是把条件变量的各个字段都初始化为 0。

pthread_cond_destroy 函数用于销毁条件变量,以释放其占用的内核 资源。 销毁 一个 正在 被 等待 的 条件 变量 将 失败 并 返回 EBUSY。

pthread_cond_broadcast 函数以广播的方式唤醒所有等待目标条件变量的线程。

pthread_cond_signal 函数用于唤醒一个等待目标条件变量的线程。至于哪个线程将被唤醒,则取决于线程的优先级和调度策略。有时候我们可能想唤醒一个指定的线程,但 pthread没有对该需求提供解决方法。不过我们可以间接 地实现该需求:定义一个能够唯一表示目标 线程 的 全局 变量, 在 唤醒 等待 条件 变量 的 线程 前 先 设置 该 变量 为 目标 线程,然后采用广播方式唤醒所有等待条件变量的线程,这些线程被唤醒后都检查该变量以判断被唤醒的是否是自己,如果是就开始执行后续代码,如果不是则返回继续等待。

pthread_cond_wait 函数用于等待目标条件变量。mutex 参数是用于保护条件变量的互斥锁,以确保 pthread_cond_wait 操作的原子性。在调用 pthread_cond_wait 前,必须确保互斥锁 mutex 已经加锁, 否则将导致不可预期的结果。 pthread_cond_wait 函数 执行时,首先把调用线程放入条件变量的等待队列中,然后将互斥锁 mutex 解锁。可见,从 pthread_cond_wait 开始执行到其调用 线程被放入条件变量的等待队列之间的这段时间内,pthread_cond_signal 和 pthread_cond_broadcast 等函数不会修改条件变量。 换言之,pthread_cond_wait 函数不会错过目标条件变量的任何变化[7]。当 pthread_cond_wait 函数成功返回时,互斥锁 mutex 将 再次被锁上。

上面这些函数成功时返回 0,失败则返回错误码。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值