线程创建函数:

#include <pthread.h>
int pthread_create(pthread_t *tid, const pthread_attr_t *addr, void *(*func)(void *), void *arg);
//成功返回0,出错则为正的Exxx值

tid 线程创建成功,其id通过tid返回,类型为pthrad_t(往往是 unsigned int)

attr 用来指定线程的属性 如:优先级,初始栈的大小,是否成为一个守护线程等等,
通常我们采用默认设置,attr置为空指针。
func 线程通过调用这个函数开始执行,然后显式终止(调用pthread_exit)或隐式终止(通过让该函数返回)
arg 线程函数调用的唯一参数是指针arg, 如果我们需要给该函数传递多个参数,就得把他们打包成一个结构,然后偶
把这个结构的地址作为单个函数传递给这个起始函数

注:func所指的函数作为参数接受一个通用指针( void *), 又作为返回值返回一个通用指针 ( void *)。
这使得我们可以把一个指针(它指向我们期望的任何内容)传递给线程,又允许线程返回一个指针(它同样指向任何期望的内容)

等待一个给定线程终止,对比unix进程,pthread_create类似于fork, pthread_join类似于waitpid

#include <pthread.h>
int pthread_join(pthread_t *tid, void **status);
//成功则为0,若出错则为正的Exxx值 等待的线程一退出就会触发该函数的调用(其他线程获取) 否则会一直中断等待 不会向下执行
必须指定等待的线程tid。pthread没办法等待任意一个线程(类似指定进程ID参数为-1调用waitpid)

status指针非空,来自等待线程的返回值(一个指向某个对象的指针)将存入由status指向的位置。

例:

#include "apue.h"
#include "myerr.h"
#include <pthread.h>
#include <iostream>
using namespace std;

void * thr_fn1(void *arg)
{
printf("thread 1 returning\n");
//cout << "thread 1 returning" << endl;
return ((void *)1); //正常退出会触发
}

void * thr_fn2(void *arg)
{
//cout << "thread 2 exiting" << endl;
printf("thread 2 returning\n");
sleep(3);
pthread_exit((void *)2);//主动退出也回触发
}

int main(void)
{
int err;
pthread_t tid1, tid2;
void *tret;

err = pthread_create(&tid1, nullptr, thr_fn1, nullptr);
if (err != 0 ) {
err_exit(err, "can't create thread 1");
}

err = pthread_create(&tid2, nullptr, thr_fn2, nullptr);
if (err != 0 ) {
err_exit(err, "can't create thread 1");
}

err =pthread_join(tid1, &tret);
if (err != 0 ) {
err_exit(err, "can't join with thread 1");
}
cout << "thread 1 exit code " << long(tret) << endl;

cout << "xxx" << endl;
err = pthread_join(tid2, &tret); //中断等待 线程2退出后立即触发 第二个参数可以是一个复杂的结构体但必须要保证 <span style="font-family: Arial, Helvetica, sans-serif;">tret的内存仍然有效 这个不应该在已经退出的线程中分配</span>

cout << "xxx222" << endl;
if (err != 0 ) {
err_exit(err, "can't join with thread 2");
}
cout << "thread 2 exit code " << long(tret) << endl;

return 0;
}


获取自身的线程ID,类似于unix进程的getpid

#include <pthread.h>
pthread_t pthread_self(void);
//返回调用线程的线程ID

pthread_detach 函数指定线程转变为脱离状态
一个线程是可汇合的(joinable,默认值),或者是脱离的(detached)。当一个可汇合的线程终止时,
他的线程id和退出的状态将留存到另一个线程对它调用pthread_join.如果一个线程需要知道另个线程什么时候终止,
那最好保持第二个线程可汇合状态。通常由想让自己脱离的线程调用。pthread_detach(pthread_self());
#include <pthread.h>
int pthread_detach(pthread_t id);
//成功则为0,出错则为正Exxx值

线程终止函数
#include <pthread.h>
void pthread_exit(void *status);
注:如果线程未脱离,线程ID和退出状态将一直留到其他线程对它调用pthread_join
status不能指向局部于调用线程对象,因为线程终止时对象会消失。
让一个线程终止的另两个方法。
1,启动线程函数(即pthread_create的第三个参数)可返回,它的返回值就是相应线程的终止状态
2,如果进程的main函数或任何线程调用exit或main函数执行完了,整个进程就终止,其中包括他的任何线程

请求取消同一进程中的其他线程

#include <pthread.h>
int pthread_cancel(pthread_t tid);
//成功返回0 错误返回错误编号
//pthread_cancel 并不是等待线程终止 仅仅提出请求 被要求停止的线程可以忽略取消或控制如何被取消

线程清理程序,一个线程可以建立多个清理程序,执行顺序与他们注册的顺序相反

#include <pthread.h>
void pthread_cleanup_push(void (*rtn)(void *), void *arg);

void pthread_cleanup_pop(int excute);
线程内使用 pthread_cleanup_push 与 pthread_cleanup_pop 必须1 1对应,否则报错

return ((void *)1) 正常退出在调用这里有点问题 需要主动退出pthread_exit((void *)1)




线程同步:

互斥量

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

int pthread_mutex_destory(pthread_mutex_t *mutex);
//两个函数 成功返回0 否则 返回错误编号

互斥量是用 pthread_mutex_t数据类型表示。使用之前必须调用 pthread_mutex_init 进行初始化,默认属性初始化互斥量 attr设为NULL

或 pthread_mutex_t lock =PTHREAD_MUTEX_INITIALIZER

如果动态分配的互斥量(如 malloc new),在释放内存前需要调用 pthread_mutex_destory


#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex); //加锁 如果已锁定 会阻塞等待解锁

int pthread_mutex_trylock(pthread_mutex_t *mutex); //加锁 未锁定返回0 加锁之后继续执行 如果已锁定 会返回EBUSY 不会阻塞

int pthread_mutex_unlock(pthread_mutex_t *mutex); //解锁 //三个函数 成功返回0 否则返回错误编号
//例:unix高级环境编程 11-10 基本加锁与解锁演练


避免死锁
1 一个线程对同一个互斥量加锁两次,就会陷入死锁状态

2 使用一个以上的互斥量时,如果允许一个线程一直占有第1个互斥量,并且在试图锁住第二个互斥量时处于阻塞状态。但拥有第二个互斥量的线程也在试图锁住第一个互斥量,两个线程都在请求另一个线程拥有的资源,两个线程都无法运行,于是产生死锁

解决办法:1 通过仔细控制互斥量加锁的顺序来避免死锁。两个互斥量A,B,所有的线程都是先锁住A 再锁住B(保持一致)

2 有时候应用程序的结构对互斥量的排序是很困难的。使用pthread_mutex_trylock接口避免死锁。pthread_mutex_trylock接口返回成功,那么就可以前进,如果不能获取锁,先释放已占有的锁,做好清理工作,然后过一段时间再重新试。


不同的锁顺序对程序有不同的影响,多线程软件设计需要在两者之间折中。如果锁的粒度太粗,就会出现很多线程阻塞等待相同锁,这并不能改善并发性。

如果锁的粒度太细,那么过度的锁开销会使系统的性能受到影响,而且代码变得复杂。在满足需求的情况下,在代码复杂性和性能之间找平衡。


愿意等待的加锁时间(阻塞的时间) mac os系统没实现

#include <pthread.h>
#include <time.h>

int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict tsptr);
//成功返回0 错误返回错误编号

时间到达超时值时,不会对互斥量进行加锁,而是返回错误码 ETIMEDOUT



读写锁

读写锁有三种状态:读模式下的加锁状态,写模式下的加锁状态,不加锁状态。

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

1 读写锁在写模式状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。

2 读写锁在读模式状态,所有试图以读模式对它加锁的线程都可以得到访问权。但是以写模式对此锁进行加锁的线程会阻塞,直到所有线程都释放读锁为止。

3 读写锁处于读模式锁住状态,一个线程试图以写模式获取锁时,读写锁会阻塞随后的读模式锁请求。避免读模式锁长期占用,而等待写模式锁的请求一直得不到满足。

读写锁也叫共享互斥锁。读模式锁住,可说成共享模式锁住。写模式锁住,可说成以互斥模式锁住。

#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

int pthread_rwlock_destory(pthread_rwlock_t *rwlock);
//两个函数 成功返回0 否则返回错误码
读写锁 使用之前必须初始化,释放他们底层的内存之前必须销毁

使用默认属性 attr置为NULL,或pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER
释放读写锁占用的内存之前,需要调用pthread_rwlock_destroy做清理工作,pthread_rwlock_init为读写锁分配资源,pthread_rwlock_destory释放这些资源,如果先释放了内存,那么分配给这个锁的资源就会丢失。


#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); //读模式下获取读写锁 阻塞
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); //写模式下获取读写锁 阻塞
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);//解锁 三个函数成功返回0 错误返回错误编号

#include <pthread.h>
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);//读模式获取读写锁 不阻塞
int pthread_rwlock_trywrlock(pthread_w_lock_t *rwlock);//写模式获取读写锁 不阻塞 //两个函数可获取读写锁时,返回0,否则返回错误码 EBUSY

读写锁并不是只用在文件读写上,其他的任何地方都可以用,比如数据的读写,等等。

带有超时的读写锁

#include <pthread.h>
#include <time.h>
int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict tsptr);
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict tsptr);
//mac os系统不支持


条件变量

条件变量是线程可用的另一种同步机制。条件变量给多个线程提供一个回合的场所。条件变量与互斥变量一起使用,允许线程以无竞争的方式等待特定的条件发生。

条件变量本身是由互斥量保护的。线程在改变条件变量之前必须首先锁住互斥量。

pthread_cond_t 数据类型表示条件变量。静态初始化可以常量初始化 PTHREAD_COND_INITIALIZER 如果是动态分配,必须用pthread_cond_init 进行初始化。

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

int pthread_cond_destory(pthread_cond_t *cond);


#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); //阻塞(睡眠等待)等待条件变为真

int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict tsptr); //给定时间内等待 超时返回一个错误码 ETIMEDOUT


#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);//至少能唤醒一个等待的线程

int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒等待该条件的所有线程 //两个函数成功返回0 错误返回错误码
给线程或条件发信号,必须注意,一定要在改变条件状态以后再给线程发信号。

自旋锁

自旋锁与互斥量类似,但它不是通过休眠使线程阻塞。而是在获取锁之前一直处于忙等(自旋)阻塞状态。这种情况cpu不能做其他的事情。

适用于以下情况:锁被持有的时间短,而且线程并不希望在重新调度上花费太多的成本。

用户层,自旋锁不是非常有用。

#include <pthread.h>

int pthread_spin_init(pthread_spinlock_t *lock, int pshared);

int pthread_spin_destroy(pthread_spinlock_t *lock);

自锁锁特有属性。支持线程共享同步的平台才会用到。pshared表示进程共享属性,设置为PTHREAD_PROCESS_SHARED,则自旋锁可以被访问锁底层内存的线程锁获取。

pshared 参数设置为 PTHREAD_PROCESS_PRIVATE,自旋锁只能被进程内部的线程所访问。


#include <pthread.h>

int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);
//所有函数 正确返回0,否则返回错误编号
_lock获取锁之前一直自旋(阻塞),trylock如果不能获取锁,就立即返回EBUSY错误。


屏障

屏障是用户协调多个线程并行工作的同步机制。屏障允许每个线程等待,知道所有的合作线程都到达某一点,然后从该点继续执行。

允许任意数量的线程等待,知道所有的线程完成处理工作,而线程不需要退出,所有线程到达屏障后可接着工作。

#include <pthread.h>
int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attire, unsigned int count);
//count 参数指定,在允许所有线程运行之前,必须到达屏障的线程数目
int pthread_barrier_destroy(pthread_barrier_t *barrier);
//两个函数 成功返回0 错误返回错误编号


#include <pthread.h>

int pthread_barrier_wait(pthread_barrier_t *barrier);
//成功返回0或 PTHREAD_BARRIER_SERIAL_THREAD, 否则返回错误编号
表明线程已完成工作,准备等待(睡眠阻塞)所有的线程赶上来。如果最后一个线程调用pthread_barrier_wait线程,就满足了屏障计数,所有的线程都被唤醒。

对于任意一个线程,pthread_barrier_wait函数返回了 PTHREAD_BARRIER_SERIAL_THREAD.剩下线程返回0,使得一个线程可作为主线程,他可以工作在其他所有线程已完成的工作结果上。

一旦达到屏障计数值,线程处于非阻塞状态,屏障就可以被重用。除非调用destroy之后,又重新 init。

用户举例:使用8的线程分解800万个数据排序工作,每个线程对100万数据排序,然后主线程调用一个函数对这些结果进行合并。

单个线程需要12.14秒,8个线程和一个合并结果线程,仅需1.91秒,速度提升了6倍。



更多相关文章

  1. linux线程函数中代替sleep的方法
  2. Linux下cfsetospeed和cfsetispeed函数
  3. 字符串处理函数strcat和strtok
  4. Linux的时间函数(转载)
  5. linux进程和线程排查 · 记一次JVM CPU高负载的排查办法
  6. linux下main thread如何使用pthread_join等待子线程结束后再退出
  7. 【linux】下的mkfifo 命令 和【C语言】中的mkfifo函数
  8. linux c (4) 进程终止-exit和_exit函数
  9. 看谁能找出bug★☆open函数总是返回-1

随机推荐

  1. 用Python来找合适的妹子
  2. Python 获得最近一个月的每天的日期
  3. Python数据挖掘实例(实时更新)
  4. 如何在Python中实现GCM HTTP服务器,同时避
  5. 在Python中删除String中的引号
  6. python 爬取西刺免费代理ip 并使用telnet
  7. 搭建Python3+PyQt5+eric6平台开发基于Pyt
  8. 【python】通过代理安装包
  9. “全局变量是坏的”是什么意思?
  10. Python Django对接企业微信第三方服务回