线程pthread与POSIX信号量

一、认识主线程

  • getpid()得到的是进程的pid,在内核中,每个线程都有自己的PID,要得到线程的PID,必须用syscall(SYS_gettid)。

  • pthread_self函数获取的是线程ID,线程ID在某进程中是唯一的,在不同的进程中创建的线程可能出现ID值相同的情况。

  • 在1个进程中新建1个线程,那么就出现了2个tid与1个pid,而其中有一个 tid == pid,那么该tid就指向主线程,也即执行main函数的线程。
    在这里插入图片描述

  • 若在进程中不创建线程,那么tid == pid
    在这里插入图片描述

二、pthread的使用

2.1、创建线程

int pthread_create(pthread_t *tidp,const pthread_attr_t *attr, 
                   (void*)(*start_rtn)(void*),void *arg);

第一个参数为指向线程标识符的指针。

第二个参数用来设置线程属性。

第三个参数是线程运行函数的起始地址。

最后一个参数是运行函数的参数。

2.2.1 单个参数传递

pthread_creat()时直接将参数取地址即可。

#include <pthread.h>

void *func(void *argv){
	/* 传入参数为void类型的指针,先强制转换给int型指针,再解指针赋值给i */
	int i = *(int *)argv;
	printf("这里是线程,参数是%d\n", i);
	return NULL;
}

int main(){
	pthread_t thread1;
	int data, ret;
	/* 创建进程 */
	if ((ret = pthread_create(&thread1, NULL, func, &data) ) != 0){
		printf("创建线程失败\n");
	}
	/* 回收进程 */
	if (thread1 != 0){
		pthread_join(thread1, NULL);
	}
return 0;
}

2.2.2、多个参数传递
  • 多个参数传递时,需要先将参数整合成一个结构体,在pthread_creat()时将结构体看成一个参数,取地址传递,后半部分与上例相同。
#include <pthread.h>

typedef struct arg_struct ARG ;
  struct arg_struct {
  char name[10] ;
  int age ;
  float weight ;
} ;

void * thfn ( void * arg ){
  ARG * p = (ARG *) arg ;
  printf( " name is : %s , age is : % d , weight is : %f \ n " , p->name , p->age , p->weight ) ;
  return NULL ;
}

int main(int argc , char *argv [ ] ){
  pthread_t thread1 ;
  ARG arg ;
  int err ;
  strcpy ( arg.name , " zhanggd " ) ;
  arg.age = 26 ;
  arg.weight = 70 ;
  err = pthread_create ( &thread1 , NULL , thfn , (void *) & arg ) ;
  if( err != 0 ) {
  	printf( " can’ˉt create thread %s\n", strerror(err) ) ;
  	exit(1);
  }
  /* 回收进程 */
	if (thread1 != 0){
		pthread_join(thread1, NULL);//阻塞等待
	}
  return 0 ;
}

2.2、结束线程

√ 从线程函数return,这是最推荐的办法。

√ 一个线程可以调用pthread_cancel终止同一进程中的另一个线程:

√ 线程可以调·pthread_exit终自己;int pthread_cancel(pthread_t thread);

√ 调用pthread_kill发送一个信号终止同一进程中的另一个线程;

2.3、回收线程

2.3.1、线程结束后怎么释放资源?

Linux系统中程序的线程资源是有限的,表现为对于一个程序其能同时运行的线程数是有限的。而默认的条件下,一个线程结束后,其对应的资源不会被释放,于是,如果在一个程序中,反复建立线程,而线程又默认的退出,则最终线程资源耗尽,进程将不再能建立新的线程。

解决这个问题,有2种方式,系统自动释放线程资源,或者由另一个线程释放该线程资源。

进程运行后,本身,也是一个线程,主线程,主线程和主线程建立的线程共享进程资源。不同于其他线程,在于主线程运行结束后,程序退出,所有程序建立的线程也会退出。

2.3.2、系统自动释放

如果想在线程结束时,由系统释放线程资源,则需要设置线程属性为detach,是线程分离主线程

pthread_t tid;
pthread_attr_t attr; //线程属性
pthread_attr_init(&attr);  //初始化线程属性
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);      //设置线程属性detach
pthread_create( &tid, &attr, run, NULL);                   //建立线程
2.3.3、由其他线程释放

pthread_join( t)等待线程t退出,并释放t线程所占用的资源。

pthread_t t;
pthread_create( NULL, NULL, GetAndSaveAuthviewSDRStub, (void*)lp);
pthread_join( t);

pthread_join函数会阻塞等待指定线程退出,然后回收资源,这样就有同步的功能,使一个线程等待另一个线程退出,然后才继续运行。

2.4、线程分离

pthread有两种状态joinable状态和unjoinable状态

  • 若是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符。只有当你调用了pthread_join之后这些资源才会被释放。
  • 若是unjoinable状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放。
pthread_detach()
  • 子线程自行分离
pthread_detach(pthread_self());

主线程分离子线程

pthread_creat(&tid, NULL, func, NULL);
pthread_detach(tid);

pthread_detach(tid)函数的功能是使线程ID为tid的线程处于分离状态,一旦线程处于分离状态,该线程终止时底层资源立即被回收;否则终止子线程的状态会一直保存(占用系统资源)直到主线程调用pthread_join(tid,NULL)获取线程的退出状态。

在这里插入图片描述

三、线程间通信

由于线程间共享内存空间,则各个线程可以使用全局变量交换数据,并且注意加如同步或互斥机制。

3.1、互斥锁

3.1.1、锁的创建与属性
  • 静态创建(编译时创建)

可以用宏PTHREAD_MUTEX_INITIALIZER来静态的初始化锁,这个宏是一个结构常量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

  • 动态创建(运行时创建)

锁可以用pthread_mutex_init函数动态的创建,函数原型如下:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t * attr)

pthread_mutex_t mutex;
/* NULL参数表明使用默认属性。 */
pthread_mutex_init(&mutex, NULL);
  • 锁的共享属性

PTHREAD_PROCESS_SHARE
不同进程间共享,包括线程。

PTHREAD_PROCESS_PRIVATE
默认属性,只能进程间内使用,进程内的各个线程共享。

  • 锁的类型属性

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

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

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

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

  • 其他属性
3.1.2、锁的销毁

调用pthread_mutex_destory()之后,可以释放锁占用的资源。在Linux中,互斥锁并不占用任何资源,因此pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作

/* NULL参数表明使用默认属性。 */
pthread_mutex_init(&mutex, NULL);
/* 如果需要声明特定属性的互斥锁,须调用函数其他 */
/* 设置、读取属性pshared */
int pthread_mutexattr_getpshared (const pthread_mutexattr_t *__restrict __attr, int *__restrict __pshared)
int pthread_mutexattr_setpshared (pthread_mutexattr_t *__attr, int __pshared)
/* 设置、读取属性type */
int pthread_mutexattr_settype (pthread_mutexattr_t *__attr, int __kind)
int pthread_mutexattr_gettype (const pthread_mutexattr_t *__restrict __attr, int *__restrict __kind)
3.1.3、锁的操作

不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。

  • 对于普通锁和适应锁类型, 解锁者可以是同进程内任何线程;
  • 而检错锁则必须由加锁者解锁才有效,否则返回EPERM;
  • 对于嵌套锁,文档和实现要求必须由加锁者解锁,但实验结果表明并 没有这种限制,这个不同目前还没有得到解释。
  • 在同一进程中的线程,如果加锁后没有解锁,则任何其他线程都无法再获得锁。
/* 阻塞 */
  int pthread_mutex_lock(pthread_mutex_t *mutex)
/* 解锁 */
  int pthread_mutex_unlock(pthread_mutex_t *mutex)
/* 非阻塞 */
  int pthread_mutex_trylock(pthread_mutex_t *mutex)

3.2 信号灯

信号灯的分类:

  • 计数信号灯(也称信号量)#include<semaphore.h>
    • POSIX无名信号灯 - 线程间通信
    • POSIX有名信号灯 - 进程间通信(线程也可以)
  • SystemV信号灯(集)#include<sys/sem.h>
    • 1个或多个技计数信号灯集合
3.2.1、POSIX有名、无名信号量
  • POSIX有名信号灯,基于文件(位于/dev/shm),多用于进程间通信。

  • POSIX无名信号灯,没有实体文件,基于内存(类似于普通变量),只用于线程间通信。

  • 两者的方法仅仅是打开与关闭的方法不同而已
    在这里插入图片描述

  • sem_open()、sem_close()、sem_unlink()函数:

#include <semaphore.h>

// 创建一个新的有名信号量或打开一个已存在的有名信号量
// 成功返回指向信号量的指针,出错返回SEM_FAILED
sem_t *sem_open(const char *name, int oflag, /* mode_t mode, unsigned int value */);
// name:IPC 名字,可能是某个文件系统中的一个真正路径名,也可能不是
// oflag: 可以是0、O_CREAT、O_CREAT|O_EXCL
// 在oflag指定了O_CREAT标志后,mode、value参数必须指定(mode指定权限位,value指定信号量初值)

// 关闭一个由 sem_open 打开的信号量
// 成功返回0,出错返回-1
int sem_close(sem_t *sem);

// 将某个有名信号量从系统删除
// 成功返回0,出错返回-1
int sem_unlink(const char *name);
  • sem_wait()、sem_trywait()函数:
#include <semaphore.h>

// 测试指定信号量的值,若该值大于0,则将其减1并立即返回,若该值为0,则
// 线程被投入睡眠,等待该值变为大于0,再将其减1并返回 
// 成功返回0,出错返回-1
int sem_wait(sem_t *sem);

// 
// 测试指定信号量的值,若该值大于0,则将其减1并立即返回,若该值为0,则
// 返回一个EAGAIN错误
// 成功返回0,出错返回-1
int sem_trywait(sem_t *sem);
  • sem_post()函数:
#include <semaphore.h>

// 在线程使用完某个信号量时,调用sem_post(),该函数将指定信号量的值加1,
// 然后唤醒等待该信号量值变为正数的任意线程
// 成功返回0,出错返回-1
int sem_post(sem_t *sem);
  • sem_getvalue()函数:
// 通过valp返回指定信号量的当前值,若当前信号量已上锁,则返回0或某个
// 负数(其绝对值为等待该信号量解锁的线程数)
// 成功返回0,出错返回-1
int sem_getvalue(sem_t *sem, int *valp);
  • sem_init()、sem_destroy函数:
#include <semaphore.h>

// 初始化一个基于内存的信号量(sem指向的)
int sem_init(sem_t *sem, int shared, unsigned int value);
// sem参数指向应用程序分配的sem_t变量
// shared参数为0时,待初始化的信号量是在同一进程的各个线程中共享的(具有随进程的持续性)
// shared参数非0时,待初始化的信号量必须放在某种类型的共享内存区中,想
// 要是由此信号量的所有进程都需要可以访问该共享内存区(随该共享内存区持续)
// value参数为该信号量的初始值

// 摧毁指定的基于内存的信号量
// 成功返回0,出错返回-1
int sem_destroy(sem_t *sem);
3.2.2、SystemV信号灯集
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值