Linux 多线程常用知识点总结

Linux 多线程常用函数笔记

线程函数使用

【TID的类型: pthread_t】

  • pthread_t是一个结构体数据类型,所以可移植操作系统实现不能把它作为整数处理
  • Linux 3.2.0使用无符号长整型表示pthread_t数据类型

【线程TID的比较:pthread_equal】

#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2);
// 若相等,返回非0数值;否则,返回0

【获得自身的线程ID:pthread_self】

  • 使用printf打印时,使用“%lu”打印pthread_t类型的值
#include <pthread.h>
pthread_t pthread_self(void); 
// 返回调用线程的线程ID

【gettid()获取线程的tid】

  • 函数gettid()可以得到线程的tid,但glibc并没有实现该函数,只能通过Linux的系统调用syscall(<sys/syscall.h>)来获取
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h> 

void *hello()
{
	printf("child,the tid=%lu,pid=%ld\n",pthread_self(),syscall(SYS_gettid));
}

int main(int argc,char *agrv[])
{
	pthread_t thread_id;
	pthread_create(&thread_id,NULL,(void *)*hello,NULL);
	
	printf("parent,the tid=%lu,pid=%ld\n",pthread_self(),syscall(SYS_gettid));
	pthread_join(thread_id,NULL);
}
  • 注:
    • 使用上面的方法可以验证:主线程PID与进程PID相同
    • 当进程中没有创建线程时,主进程也就相当于一个主线程,因为进程和线程的ID都是相同的

【线程创建函数:pthread_create】

#include <pthread.h>
int pthread_create(
	pthread_t              *restrict tidp,        //当pthread_create成功返回时,新创建的线程ID会被设置到tidp所指向的内存单元中
	const pthread_attr_t   *restrict attr,        //atrr参数用于指定线程创建时的初始化属性。值为NULL时,创建一个具有默认属性的线程。
	void                   *(*start_rtn)(void *), //新创建的线程从start_rtn函数的地址开始运行。该函数只有一个无类型指针参数arg,返回值为void*类型
	void                   *restrict  arg         //如果需要向start_rtn函数传递的参数有一个以上,需要把这些参数放到一个结构中,传递该结构的地址
	);
  • 返回值:
    • 成功:返回0
    • 出错:返回错误编号,并不设置errno
  • 案例1
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
pthread_t ntid;
 
void printids(const char *s){
    pid_t pid;
    pthread_t tid;
    pid = getpid();
    tid = pthread_self();
    printf("%s pid %lu tid %lu (0x%lx)\n", s, (unsigned long)pid,(unsigned long)tid, 
    (unsigned long)tid);
}
 
void *thr_fn(void *arg){
    printids("new thread: ");
    return((void *)0);
}
 
int main(void)
{
    int err;
    //create thread
    err = pthread_create(&ntid, NULL, thr_fn, NULL);
    if (err != 0)
        printf("can’t create thread\n");
    printids("main thread:");
    sleep(1);
    exit(0);
}
  • 案例2
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h> 

struct message
{
	int i;
	int j;
};

void *hello(struct message *str)
{
	printf("child,the tid=%lu,pid=%ld\n",pthread_self(),syscall(SYS_gettid));
	printf("the arg.i is %d,arg.j is %d\n",str->i,str->j);
}
 
int main(int argc,char *agrv[])
{
	struct message test;
	pthread_t thread_id;
	test.i=10;
	test.j=20;
	pthread_create(&thread_id, NULL,(void *)*hello,&test);
	printf("parent,the tid=%lu,pid=%ld\n",pthread_self(),syscall(SYS_gettid));
	pthread_join(thread_id,NULL);
}

【线程属性结构体:pthread_attr_t】

  • 此结构的内容包含的就是线程的属性值,pthread_create的参数2就是使用此结构为线程设置初始属性值。

【线程属性结构体的创建与销毁】

int pthread_attr_init(pthread_attr_t *attr);
  • 功能:用来对线程属性值进行初始化
  • 注意:调用此函数之后,属性结构体的属性都是系统默认值,如果想要设置其他属性,还需要调用不同的函数进行设置
int pthread_attr_destroy(pthread_attr_t *attr);
  • 功能:用来反初始化(销毁)属性。如果pthread_attr_init函数初始化的线程属性是动态分配的,那么此函数就会释放线程属性的内存空间
  • pthread_attr_destory函数会用无效的值初始化属性对象,因此用此函数反初始化属性之后,属性就不能供pthread_create函数使用了.

【线程的终止方式】

  • 下面这三种方法是正常不终止整个进程的情况下,终止线程并且停止它的控制流:
    • ①线程可以简单地从启动例程中返回,返回值是线程的退出码
    • ②线程可以被同一进程中的其他线程取消(pthread_cancel
    • ③线程调用pthread_exit

【线程退出函数:pthread_exit】

#include <pthread.h>
void pthread_exit(void *rval_ptr);
  • 功能:线程调用此函数终止自己
  • 参数:rval_ptr是一个无类型指针,退出进程可以在这个指针里面设置内容(常用来设置终止码)。
    • 进程中的其他线程也可以通过调用pthread_join函数访问这个指针
#include<pthread.h>
#include<stdlib.h>
#include<stdio.h>
void * thr_fn1(void *arg){
    printf("thread 1 returning\n");
    return((void *)1);
}
void *thr_fn2(void *arg){
    printf("thread 2 exiting\n");
    pthread_exit((void *)2);
}
int main(void)
{
    int err;
    pthread_t tid1, tid2;
    void *tret;
 
    err = pthread_create(&tid1, NULL, thr_fn1, NULL);//创建线程1
    if (err != 0)
        printf("can’t create thread 1\n");
    err = pthread_create(&tid2, NULL, thr_fn2, NULL);//创建线程2
    if (err != 0)
        printf("can’t create thread 2\n");
 
    err = pthread_join(tid1, &tret);//等待线程1
    if (err != 0)
        printf("can’t join with thread 1\n");
    printf("thread 1 exit code %ld\n", (long)tret);
    
    err = pthread_join(tid2, &tret);//等待线程2
    if (err != 0)
        printf("can’t join with thread 2\n");
    printf("thread 2 exit code %ld\n", (long)tret);
   
     exit(0);
}

【线程等待函数pthread_join】

#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);
  • 返回值:成功返回0;失败返回错误编号
  • 功能:用来等待参数1指定的线程结束
  • 此函数会阻塞,直到指定的线程调用pthread_exit、从启动线程中返回、或者被取消,此函数才返回
  • 参数:
    • 参数1:指定等待的线程的ID
    • 参数2:
      • 填NULL:获取等待线程的终止信息,如果对线程的终止信息并不敢兴趣,可以设置为NULL。
      • 非空:如果线程简单地从它的返回例程中返回,rval_ptr 就包含返回码。
      • 如果线程被取消,由rval_ptr指定的内存单元就被设置为PTHREAD_CANCELED
  • thread_join函数的使用与线程分离的关系:
    • 调用pthread_join等待某一线程,被等待的线程结束之后就会被置于分离状态,这样线程所使用的资源就可以恢复
    • 如果调用pthread_join等待一个线程时,如果线程已处于分离状态(例如调用pthread_detach函数),pthread_join调用会失败,返回EINVAL,尽管这种行为是与具体实现相关的

【线程取消函数:pthread_cancel】

#include <pthread.h>
int pthread_cancel(pthread_t tid);
  • 返回值:成功返回0;否则返回错误编号
  • 功能:线程可以通过pthread_cancel来请求取消同一进程中的其它线程
  • pthread_cancel并不等待线程终止,它仅仅提出请求
  • 参数:
    • 需要取消的线程ID
  • 注意事项:
    • 默认情况下,pthread_cancel函数会使得由tid标识的线程的行为表现为如同调用了参数为PTHREAD_CANCELED的pthread_exit函数,但是被取消的线程可以选择忽略取消或者控制如何被取消

【线程取消点】

  • 概念:系统自定义了一些线程取消点。当一个线程接收到其他线程的取消请求时,如果还没有运行到取消点,该线程还是会继续运行,直到运行到某个取消点,线程才真正地被取消线程取消点技术也称为“推迟取消”
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
 
void *thread_func(void* arg);
 
int main()
{
    pthread_t tid;
    int *thread_exit_code=malloc(sizeof(int));
    if(pthread_create(&tid,NULL,thread_func,NULL)!=0){
        fprintf(stdout,"pthread_create error\n");
        exit(EXIT_FAILURE);
    }
 
    //进程休眠1秒,然后取消子线程
    sleep(1);
    if(pthread_cancel(tid)!=0){
        fprintf(stdout,"pthread_cancel error\n");
        exit(EXIT_FAILURE);
    }
 
    printf("pthread_cancel filaed\n");
    
    //睡眠8秒之后取消线程失败了,因为线程已经退出了
    sleep(8);
    if(pthread_cancel(tid)!=0){
        fprintf(stdout,"pthread_cancel error\n");
        exit(EXIT_FAILURE);
    }
    
    printf("kill thread success\n");
 
    if(pthread_join(tid,(void*)&thread_exit_code)==0){
        printf("pthread_join success,exit_code is %d\n",(int)*thread_exit_code);
    }else{
        fprintf(stdout,"pthread_join error\n");
        exit(EXIT_FAILURE);
    }
 
    exit(0);
}
 
void *thread_func(void* arg)
{
    int exit_code,i;
    
    //进入之后,先设置自己为不可取消状态
    printf("I am thread,now my cancle type is disable\n");
    if(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL)!=0){
        fprintf(stdout,"pthread_setcancelstate error\n");
        exit_code=-1;
        pthread_exit(&exit_code);
    }
   
 
    for(i=1;i<=3;i++){
        sleep(1);
        printf("thread running (%d)\n",i);
    }
 
    //休眠3秒之后之后设置线程可以被取消
    printf("I am thread,now my cancle type is enable\n");
    if(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL)!=0){
        fprintf(stdout,"pthread_setcancelstate error\n");
        exit_code=-1;
        pthread_exit(&exit_code);
    }
    
    printf("thread sleep....\n");
    sleep(20);
 
    pthread_exit(NULL);
}

【线程的分离:pthread_detach】

  • 如果线程未分离:线程的终止状态会一直保存,直到另外一个线程对该线程调用pthread_join获取其终止状态,终止状态才会被释放
  • 如果线程已经被分离:线程的底层存储资源可以在线程终止时立即被回收
  • 在线程被分离后,我们不能用pthread_join函数等待它的终止状态,因为对分离状态的线程调用pthread_join会产生未定义行为
#include <pthread.h>
int pthread_detach(pthread_t tid);
  • 返回值:成功返回0;否则返回错误编号 
  • 功能:用来分离线程
  • 参数:要分离的线程tid

【互斥量:pthread_mutex_t】

  • 互斥量从本质上说是一把锁,在访问共享资源前对互斥量进行设置(加锁),在访问完成后释放(解锁)互斥量
  • 互斥变量的初始化与释放
  • ①静态初始化
    • 直接把pthread_mutex_t互斥变量设置为常量PTHREAD_MUTEX_INITIALIZER
    • 静态初始化互斥变量只能拥有默认的互斥量属性,不能设置其他互斥量属性
pthread_mutex_t  mutex;
mutex=PTHREAD_MUTEX_INITIALIZER;
//或者
pthread_mutex_t *mutex=(pthread_mutex_t *)malloc(sizeof(pthread_mutex_t));
*mutex=PTHREAD_MUTEX_INITIALIZER;
  • ②动态初始化
    • 静态初始化互斥变量只能拥有默认互斥量属性,我们可以通过pthread_mutex_init函数来动态初始化互斥量,并且可以在初始化时选择设置互斥量的属性
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 返回值:成功返回0,否则返回错误编号
  • pthread_mutex_init:
    • 功能:对互斥量进行初始化
    • 参数:
      • 参数1: 需要初始化互斥量
      • 参数2:初始化时互斥量的属性。如果使用默认属性,此处填NULL
  • pthread_mutex_destroy:
    • 功能:互斥量的反初始化
    • 参数:互斥量
    • 备注(重点):此函数只是反初始化互斥量,并没有释放内存空间,如果互斥量是通过malloc等函数申请的,那么需要在free掉互斥量之前调用pthread_mutex_destroy函数
pthread_mutex_t mutex;
pthread_mutex_init(&mutex,NULL); 
/*do something*/
pthread_mutex_destroy(&mutex);

pthread_mutex_t* mutex=(pthread_mutex_t*)malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(mutex, NULL);
/*do something*/
pthread_mutex_destroy(mutex);
free(mutex);

【互斥量的加锁与解锁】

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);  //对互斥量进行加锁。如果互斥量已经上锁,调用线程将阻塞到互斥量被解锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);  //对互斥量进行解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex); 
  • 对互斥量进行尝试加锁(非阻塞)。如果互斥量处于未加锁状态,那么pthead_mutex_trylock就会锁住这个互斥量;如果此锁处于加锁状态,那么pthead_mutex_trylock就出错返回EBUSY,并且不会阻塞

【引用计数案例】

struct foo {
    int f_count;
    pthread_mutex_t f_lock;
    int f_id;
};
 
struct foo *foo_alloc(int id)
{
    struct foo *fp;
 
    if ((fp == malloc(sizeof(struct foo))) != NULL) {
        fp->f_count = 1;
        fp->f_id = id;
        if (pthread_mutex_init(&fp->f_lock, NULL) != 0) {
            free(fp);
            fp = NULL;
        }
    }
 
    return fp;
}
 
void foo_hold(struct foo *fp)
{
    pthread_mutex_lock(&fp->f_lock);
    fp->f_count++;
    pthread_mutex_unlock(&fp->f_lock);
}
 
void foo_rele(struct foo *fp)
{
    pthread_mutex_lock(&fp->f_lock);
    if (--fp->f_count == 0) {
        pthread_mutex_unlock(&fp->f_lock);
        pthread_mutex_destory(&fp->f_lock);
        free(fp);
    } else {
        pthread_mutex_unlock(&fp->f_lock);
    }
}

【超时互斥锁:pthread_mutex_timedlock】

#include <pthread.h>
#include <time.h>
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,const struct timespec *restrict tsptr);
  • 返回值:成功返回0,否则返回错误编号.
  • 功能:当线程试图获取一个已加锁的互斥量时,pthread_mutex_timedlock允许绑定线程阻塞时间
  • 参数:
    • mutex:尝试要加锁的互斥量
    • tsptr:设置的超时时间
  • 特点:在达到超时时间值时,如果还不能对互斥量成功加锁,那么就返回错误码ETIMEDOUT
  • 超时时间的注意:超时指定愿意等待的绝对时间。这个时间值是一个绝对数而不是相对数。
  • 例如,假设愿意等待3分钟,不是把3分钟转换为timespec结构体传递给参数2,而是需要把当前时间加上3分钟再转换成timespec结构然后传递给参数2

【pthread_mutex_timedlock避免永久阻塞案例】

#include <pthread.h>
 
int main(void)
{
    int err;
    struct timespec tout;
    struct tm *tmp;
    char buf[64];
 
    pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    pthread_mutex_lock(&lock);
    printf("mutex is locked\n");
	
    clock_gettime(CLOCK_REALTIME, &tout);
	
    tmp = localtime(&tout.tv_sec);
    strftime(buf, sizeof(buf), "%r", tmp);
    printf("current time is %s\n", buf);
	
    tout.tv_sec += 10; /* 10 seconds from now */
    
    /* caution: this could lead to deadlock */
    err = pthread_mutex_timedlock(&lock, &tout);
	
    clock_gettime(CLOCK_REALTIME, &tout);
    tmp = localtime(&tout.tv_sec);
    strftime(buf, sizeof(buf), "%r", tmp);
    printf("the time is now %s\n", buf);
	
    if (err == 0)
        printf("mutex locked again!\n");
    else
        printf("can’t lock mutex again: %s\n", strerror(err));
    exit(0);
}
  • 这个程序故意对它已有的互斥量进行加锁,目的是演示pthread_mutex_timedlock是如何工作的。不推荐在实际中使用这种方法,因为会导致死锁
  • 注意:阻塞的时间可能会有所不同,造成不同的原因有多种:开始时间可能在某秒的中间位置,系统时钟的精度可能不足以精确到支持我们指定的超时时间值,或者在程序继续运行前,调度延迟可能会增加时间值

【条件变量:pthread_cond_t】

  • 条件变量是线程可用的另一种同步机制
  • 条件变量给多个线程提供了一个会合的场所
  • 条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生
  • 条件变量是线程中的东西,就是等待某一条件的发生,和信号一样
  • 条件变量要与互斥量一起使用,条件本身是由互斥量保护的。线程在改变条件状态之前必须首先锁住互斥量
  • 其他线程在获得互斥量之前不会察觉到这种改变,因为互斥量必须在锁定以后才能计算条件
  • ①静态初始化
    • 直接把pthread_cond_t定义的条件变量设置为常量PTHREAD_COND_INITIALIZER
    • 静态初始化条件变量只能拥有默认的条件变量属性,不能设置其他条件变量属性
pthread_cond_t cond;
cond=PTHREAD_COND_INITIALIZER;
//或者
pthread_cond_t *cond=(pthread_cond_t *)malloc(sizeof(pthread_cond_t));
*cond=PTHREAD_COND_INITIALIZER;
  • ②动态初始化
    • 静态初始化条件变量只能拥有默认条件变量属性,我们可以通过pthread_mutex_init函数来动态初始化条件变量,并且可以在初始化时选择设置条件变量的属性
#include <pthread.h>
int pthread_cond_init(pthread_cond_t* restrict cond,const pthread_condattr_t* restrict attr);
int pthread_cond_destroy(pthread_cond_t* cond);
  • 返回值:成功返回0;失败返回错误编号
  • pthread_cond_init:
    • 功能:对条件变量初始化
    • 参数:
      • 参数1: 需要初始化的条件变量
      • 参数2:初始化时条件变量的属性。如果使用默认属性,此处填NULL
  • pthread_cond_destroy:
    • 功能:对条件变量反初始化(在条件变量释放内存之前)
    • 参数:条件变量
    • 备注(重点):此函数只是反初始化互斥量,并没有释放内存空间,如果互斥量是通过malloc等函数申请的,那么需要在free掉互斥量之前调用pthread_mutex_destroy函数
pthread_cond_t cond;
pthread_cond_init(&cond,NULL);
/*do something*/
pthread_cond_destroy(&cond);

pthread_cond_t * cond=(pthread_cond_t *)malloc(sizeof(pthread_cond_t));
pthread_cond_init(cond,NULL);
/*do something*/
pthread_cond_destroy(cond);
free(cond);

【等待条件变量函数】

#include <pthread.h>
int pthread_cond_wait(pthread_cond_t* restrict cond,pthread_mutex_t* restrict mutex);
int pthread_cond_timedwait(pthread_cond_t* cond,pthread_mutex_t* restrict mutex,const struct timespec* restrict tsptr);
  • 返回值:成功返回0;失败返回错误编号
  • 这两个函数调用成功返回时,线程需要重新计算条件,因为另一个线程可能已经在运行并改变了条件
  • pthread_cond_wait
    • 注意:等待条件变量变为真
    • 如何使用:参数mutex互斥量提前锁定,然后该互斥量对条件进行保护,等待参数1cond条件变量变为真。在等待条件变量变为真的过程中,此函数一直处于阻塞状态。但是处于阻塞状态的时候,mutex互斥量被解锁(因为其他线程需要使用到这个锁来使条件变量变为真)
    • 当pthread_cond_wait函数返回时,互斥量再次被锁住
  • pthread_cond_timedwait
    • pthread_cond_timedwait函数与pthread_cond_wait函数功能相同。不过多了一个超时参数。超时值指定了我们愿意等待多长时间,它是通过timespec结构体表示的
    • 如果超时到期之后,条件还是没有出现,此函数将重新获取互斥量,然后返回错误ETIMEOUT
    • 注意:这个时间值是一个绝对数而不是相对数。例如,假设愿意等待3分钟,那么不是把3分钟转换为timespec结构体,而是需要把当前时间加上3分钟再转换成timespec结构

【得到超时值的绝对时间获取函数】

  • 可以使用clock_gettime函数获取timespec结构表示的当前时间。但是目前并不是所有的平台都支持这个函数。因此,可以使用gettimeofday获取timeval结构表示的当前时间,然后把这个时间转换为timespec结构
#include <sys/time.h>
#include <stdlib.h>
 
void maketimeout(struct timespec *tsp, long minutes)
{
	struct timeval now;
 
	/* get the current time */
	gettimeofday(&now, NULL);
	
	tsp->tv_sec = now.tv_sec;
	tsp->tv_nsec = now.tv_usec * 1000; /* usec to nsec */
	/* add the offset to get timeout value */
	tsp->tv_sec += minutes * 60;
}

【条件变量信号发送函数】

#include <pthread.h>
int pthread_cond_signal(pthread_cond_t* cond);    //至少能唤醒一个等待该条件的线程
int pthread_cond_broadcast(pthread_cond_t* cond); //唤醒等待该条件的所有线程
  • 返回值:成功返回0;失败返回错误编号
  • 这两个函数用于通知线程条件变量已经满足条件(变为真)。在调用这两个函数时,是在给线程或者条件发信号
  • 必须注意:一定要在改变条件状态以后再给线程发信号

【结合使用条件变量和互斥量对线程进行同步】

#include <pthread.h>
 
struct msg {
	struct msg *m_next;
	/* ... more stuff here ... */
};
 
struct msg  *workq;
pthread_cond_t  qready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;
 
void  process_msg(void)
{
	struct msg *mp;
 
	for (;;) {
		pthread_mutex_lock(&qlock);
		while (workq == NULL)
			pthread_cond_wait(&qready, &qlock);
		mp = workq;
		workq = mp->m_next;
		pthread_mutex_unlock(&qlock);
		/* now process the message mp */
	}
}

void  enqueue_msg(struct msg *mp)
{
	pthread_mutex_lock(&qlock);
	mp->m_next = workq;
	workq = mp;
	pthread_mutex_unlock(&qlock);
	pthread_cond_signal(&qready);
}

猜你喜欢

转载自blog.csdn.net/baidu_41388533/article/details/114851779