Linux多线程开发

文章介绍了进程与线程的区别,包括它们在资源分配、通信方式和健壮性方面的差异。接着详细讲述了线程的创建、终止、获取线程ID、线程资源的回收方法,如pthread_create、pthread_exit、pthread_join和pthread_cancel。还讨论了线程的属性设置,如可结合和可分离线程。最后,文章提到了线程同步的重要性,包括并发与并行的概念,以及线程同步的几种方法,如互斥锁,并初步介绍了死锁和不同类型的锁。
摘要由CSDN通过智能技术生成

一、线程

1、进程和线程的区别和联系?

本质区别:进程是操作系统资源分配的基本单位;而线程是处理器任务调度和执行的基本单位。

包含关系:一个进程至少有一个线程,线程是进程的一部分,所以线程也被称为轻量级进程。

资源开销:进程和线程的切换都是由操作系统控制(用户态→内核态→用户态)。进程的上下文切换不仅包含了栈、堆、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的资源。当两个线程是属于同一个进程,因为大部分内存是共享的,所以在切换时,只需要切换线程的栈、寄存器等不共享的数据。

通信方面:进程间通信需要借助操作系统;线程间可以直接读写进程数据段(如全局变量)来进行通信。

影响关系:一个进程崩溃后,在保护模式下其他进程不会被影响,但是一个线程崩溃可能导致整个进程被操作系统杀掉,所以多进程要比多线程健壮。

2、线程之间共享和非共享资源

二、线程操作

1.创建线程

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

功能:创建一个子线程

参数:

  thread:传出参数,线程创建成功后,子线程的线程ID被写到该变量中;

  attr:设置线程的属性,一般使用默认值,NULL;

  start_routine:函数指针,这个函数是子线程需要处理的逻辑代码;

  arg:传参,给第三个参数使用。

返回值:

  成功返回0,失败返回错误号。

类内使用pthread_create函数时,函数指针必须指向一个静态函数。C++类中的成员函数默认在参数中包含有一个this指针,这样成员函数才知道应该对哪个实例作用。而线程函数必须接受一个void指针作为参数,而C++无法发生void*和this的转换,所以产生了矛盾。为了解决矛盾,我们可以使用static函数,它独立于实例,参数中将不会有this指针,所以可以用于打开线程。

如果需要调用调用类内的成员变量、成员函数,可以通过类的对象作为参数传递给该静态函数,然后在静态函数中引用这个对象,并调用其动态方法。

2.终止线程

void pthread_exit(void* retval)

功能:终止一个线程,在哪个线程中调用,就表示终止哪个线程;

参数:

  retval:需要传递一个指针作为返回值,可以在pthread_join()中获取;

3.获取线程ID

pthread_t pthread_self(void)

功能:获取当前线程的线程ID;

返回值:返回线程ID;

4.主线程回收子线程资源

int pthread_join(pthread_t thread, void** retval)

功能:一般在主线程中使用,和一个已经终止的线程进行连接,回收子线程的资源。调用时阻塞函数,直到回收资源,调用一次只能回收一个子线程。

参数:

  thread:需要回收的子线程的ID;

  retval:接收子线程退出时的返回值;

返回值:成功返回0,失败返回错误号。

5.线程分离,子线程释放资源

int pthread_cancel(pthread_t thread)

功能:分离一个线程,被分离线程在终止时会自动释放资源返回给系统。主线程不能多次分离,不能去连接已分离的线程。

参数:

  thread:需要分离的线程ID

返回值:成功返回0,失败返回错误号。

6.线程取消

int pthread_cancel(pthread_t thread)

功能:让线程终止,取消某个线程可以终止某个线程运行,但是不是立马终止,而是当子线程执行到一个取消点,线程才会终止(取消点:系统规定的系统调用,粗略理解为从用户区到内核区的系统切换的点)。

参数:

thread:需要终止的线程ID

返回值:成功返回0,失败返回错误号。

回收子线程资源的方法有两种:

第一种:主线程主动回收。调用pthread_join()函数等待子线程退出,回收它的资源,默认采用阻塞的方式,直到成功回收后才会返回,类似于进程中wait/waitpid回收僵尸进程pthread_tryjoin_np()为非阻塞方式。

第二种:设置成分离子线程。在主线程中,在创建子线程前设置子线程为分离属性,pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED),或者主线程调用detach()函数分离子线程,子线程退出时,系统将自动回收资源。分离后的子线程不可join()。

void* callback(void* arg) {
  int val = *(int*)arg
  printf("tid:%ld, arg val:%d", pthread_self(), val);
  pthread_exit((int*)&val);
}
int main() {
  pthread_t tid;
  int num = 10;
  pthread_create(&tid, NULL, callback, (void*)&num);
  if(ret != 0) {
    char* errrstr = strerror(ret);
    printf(“error1:%s\n”, errstr);
  }
  int *thread_retval;
  ret = pthread_join(tid, void(**)&retval); //回收子线程的资源
  printf("exit data:%d\n", *thread_retval);
  printf("回收子线程资源成功!\n");
  pthread_exit(NULL);
}

7.线程属性设置

线程分为可结合和可分离两种,线程默认是PTHREAD_CREATE_JOINABLE(可结合的)。可结合的线程在线程退出后不会立即释放资源,必须要调用pthread_join来显式的结束线程。可以设置子线程为PTHREAD_CREATE_DETACHED(可分离的),分离的线程在线程退出时系统会自动回收资源。

int pthread_attr_init(pthread_attr_t* attr); //功能:初始化线程属性变量
int pthread_attr_destory(pthread_attr_t* attr); //功能:释放线程属性的资源
int pthread_attr_getdetachstate(const pthread_attr_t* attr, int* detachstate); //功能:获取线程分离的状态属性
int pthread_attr_setdetachstate(const pthread_attr_t* attr, int* detachstate); //功能:设置线程分离的状态属性
void* callback(void* arg) {
  printf("child thread id:%ld\n", pthread_self());
  pthread_exit(NULL);
}
int main() {
  pthread_attr_t attr; //创建线程属性变量
  pthread_attr_init(&attr); //初始化属性变量
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //设置线程分离

  pthread_t tid;
  int ret = pthread_create(&tid, &attr, callback, NULL); //创建子线程
  if(ret != 0) {
    char *errstr = strerror(ret);
    printf("error1:%s\n", errstr); //输出错误号
  }
  printf("子线程:%ld, 主线程:%ld\n", tid, pthread_self());
  pthread_attr_destroy(&attr);
  pthread_exit(NULL);
}

三、线程同步

1.并发和并行的区别

并行:在同一时刻,有多条指令在多个处理器(CPU)上同时执行,所以无论从微观还是从宏观来看,二者都是一起执行的。

并发:在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。

2.线程同步的四种方法?

临界区、互斥量、信号量、事件。

临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。

互斥量:多个线程在同一时刻只有一个进程能进入临界区。

信号量:允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。

事件:事件对象通过通知操作的方式来保持线程的同步,并且可以实现不同进程中的线程同步操作。

3.死锁

死锁是指两个(多个)线程相互等待对方数据的过程。

死锁产生的四个必要条件:资源不共享、请求和保持、不可剥夺(资源不能被其他进程强行夺走,只能自己释放)、循环等待(进程资源循环等待链,链中每个进程已获得的资源同时被链中下一个进程所请求)

4.互斥锁、自旋锁、读写锁、乐观锁和悲观锁

互斥锁加锁失败后,线程会释放CPU,给其他线程,自身处于阻塞状态。

自旋锁加锁失败后,线程会忙等待(while),直到它拿到锁。

读写锁:读者可以同时读,写者之间、写者与读者之间互斥,写者优先读者。

乐观锁:先修改完共享资源,再验证这段时间内有没有发生冲突,如果没有其他线程在修改资源,那么操作完成,如果发现有其他线程已经修改过这个资源,就放弃本次操作。

悲观锁:多线程同时修改共享资源的概率比较高,于是很容易出现冲突,所以访问共享资源前,先要上锁。

5.互斥锁pthread_mutex_t的实现

int pthread_mutex_init(pthread_mutex_t* restrict mutex, const pthread_mutex attr_t* restrict attr)

功能:初始化互斥量

参数:

  mutex:需要初始化的互斥量变量

  attr:互斥量相关的属性,一般设置为NULL。其中restrict是C语言的修饰符,被修饰的指针不能由另外的一个指针进行操作。

返回值:成功返回0,其他返回值表示错误。

int pthread_mutex_destory(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); //解锁。
//写一个互斥类
class locker {
public:
  locker() {
    if(pthread_mutxt_init(&m_mutex, NULL) != 0) {
      throw exception();
    }
  }
  ~locker() {
    pthread_mutex_destroy(&m_mutex);
  }
  bool lock() {
    return pthread_mutex_lock(&m_mutex) == 0;
  }
  bool unlock() {
    return pthread_mutex_unlock(&m_mutex) == 0;
  }
  pthread_mutex_t* get() {
    return &m_mutex;
  }
private:
  pthread_mutex_t m_mutex;
}

条件变量、信号量待更新。。。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值