首页 > 其他分享 >线程

线程

时间:2023-03-01 19:25:04浏览次数:41  
标签:rwlock int mutex pthread 线程 sem

线程

1. 线程概述

进程是CPU分配资源的最小单位,线程是操作系统调度执行的最小单位。

线程是轻量级的进程,在Linux环境下线程的本质仍是进程。

线程和进程的区别:

  1. 进程间的信息难以共享。由于除去只读代码段外,父子进程并未共享内存,因此必须采用一些进程间通信方式,在进程间进行信息交换。
  2. 调用 fork() 来创建进程的代价相对较高,即便利用写时复制技术,仍然需要复制诸如内存页表和文件描述符表之类的多种进程属性,这意味着 fork() 调用在时间上的开销依然不菲。
  3. 线程之间能够方便、快速地共享信息。只需将数据复制到共享(全局或堆)变量中即可。创建线程比创建进程通常要快 10 倍甚至更多。
  4. 线程间是共享虚拟地址空间的,无需采用写时复制来复制内存,也无需复制页表。

1.1 线程的相关函数

/*一般情况下, main函数所在的线程称之为主线程(main线程),其余创建的线程称之为子线程
程序中默认只有一个进程,fork()函数调用,2进程
程序中默认只有一个线程,pthread_create函数调用,2个线程
*/
    #include <pthread.h>
    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, 失败错误号。这个错误号和之前的errno不太一样,  实现一样,但体系不是一个
        获取错误号信息:  char *strerror(int errnum);

    Compile and link with -pthread.  编译链接时需要链接到-pthread库

主线程退出后子线程的状态依赖于它所在的进程,如果进程没有退出的话子线程依然正常运转。如果进程退出了,那么它的所有线程都会退出
*/


#include <pthread.h>
void pthread_exit(void *retval);
功能;终止一个线程,在哪个线程中调用就表示终止哪个线程
参数: 
  retval: 需要传递一个指针,作为一个返回值,可以在pthread_join()中获取到这个返回值
   
pthread_t pthread_self(void);
功能: 获取当前线程的id

int pthread_equal(pthread_t t1, pthread_t t2);
功能:比较两个线程id是否相等
  	 不同的操作系统,pthread_t的实现不一样,有的是无符号的长整型,有的是结构体实现的,所以不能用==
 #include <pthread.h>
 int pthread_join(pthread_t thread, void **retval);
    功能: 和一个已经终止的线程进行连接
        回收子线程的资源
            这个函数是阻塞函数,调用一次只能回收一个子线程
            一般在主线程中使用
    参数:
        -thread: 需要回收的子线程的id
        -retval: 接收子线程退出时候的返回值(return xxx)  
          
#include <pthread.h>
int pthread_detach(pthread_t thread);
    功能: 分离一个线程。被分离的线程在终止的时候,会自动释放资源返回给系统, 是非阻塞的
        1. 不能多次分离,会产生不可预料的行为
        2. 不能去连接join一个已经分离的线程,会报错
    参数:
        thread: 需要分离的线程ID
    返回值: 成功0, 失败返回错误号
          
#include <pthread.h>
int pthread_cancel(pthread_t thread);
    功能: 取消线程,让线程终止
        取消某个线程,可以终止某个线程的运行  【前提是可以取消,有两个属性  指定是否可以取消】
        但是并不是立马终止,而是当子线程运行到一个取消点,线程才会终止
        取消点: 系统规定好的的一些系统调用,我们可以粗略的理解从用户区到内核区的切换,这个位置称之为取消点    
      

1.2 线程属性相关的函数

// 线程属性类型 pthread_attr_t
    int pthread_attr_init(pthread_attr_t *attr);
     - 初始化线程属性变量

    int pthread_attr_destroy(pthread_attr_t *attr);
     - 释放线程属性的资源

    int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
     - 获取线程分离的状态属性

    int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
     - 设置线程分离的状态属性

    detachstate:
        PTHREAD_CREATE_DETACHED     设置线程分离
        PTHREAD_CREATE_JOINABLE    可连接的,不分离的,默认是这个

2. 线程同步

临界区是指访问某一共享资源的代码片段,并且这段代码的执行应为原子操作,也就是
同时访问同一共享资源的其他线程不应终止该片段的执行。

线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作,而其他线程则处于等待状态。

2.1互斥量

mutex 由互斥量包含在内的代码(即临界区)最多只能有1个线程访问,不能有两个及以上的线程同时访问临界区

为避免线程更新共享变量时出现问题,可以使用互斥量(mutex 是 mutual exclusion的缩写)来确保同时仅有一个线程可以访问某项共享资源。可以使用互斥量来保证对任意共享资源的原子访问。

互斥量有两种状态:已锁定(locked)和未锁定(unlocked)。任何时候,至多只有一个线程可以锁定该互斥量。试图对已经锁定的某一互斥量再次加锁,将可能阻塞线程或者报错失败,具体取决于加锁时使用的方法。

		// 互斥量的类型 pthread_mutex_t

    int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
        - 初始化互斥量
        - 参数:
            -mutex:需要初始化的互斥量变量
            -attr: 互斥量相关的属性   NULL
        restrict  C语言的修饰符,被修饰的指针不能由另外的一个指针操作

    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);
        - 解锁

2.2 死锁

有时,一个线程需要同时访问两个或更多不同的共享资源,而每个资源又都由不同的互斥量管理。当超过一个线程加锁同一组互斥量时,就有可能发生死锁。

两个或两个以上的进程在执行过程中,因争夺共享资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。

死锁的几种场景:

  • 忘记释放锁
  • 重复加锁
  • 多线程多锁,抢占锁资源

2.3 读写锁

在对数据的读写操作中,更多的是读操作,写操作较少,例如对数据库数据的读写应用。为了满足当前能够允许多个读出,但只允许一个写入的需求,线程提供了读写锁来实现。

读写锁的特点:

  • 如果有其它线程读数据,则允许其它线程执行读操作,但不允许写操作。
  • 如果有其它线程写数据,则其它线程都不允许读、写操作。
  • 写是独占的,写的优先级高(也可以设置读的优先级更高,但是可能发生另一方饥饿的情况,因此也可以设置成公平读写锁,用队列把请求锁的线程排队,先入先出,会更公平)。
		// 读写锁的类型 pthread_rwlock_t
    int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

2.4 条件变量

注意:条件变量本身不是锁,要和互斥锁配合使用

条件变量cond使在多线程程序中用来实现“等待-->唤醒”逻辑常用的方法,是线程间同步的一种机制。条件变量用来阻塞一个线程,直到条件被满足触发为止,通常情况下条件变量和互斥量同时使用。

一般条件变量有两个状态:

(1)一个/多个线程为等待“条件变量的条件成立“而挂起;

(2)另一个线程在“条件变量条件成立时”通知其他线程。

	// 条件变量的类型 pthread_cond_t
	int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
	int pthread_cond_destroy(pthread_cond_t *cond);

	int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
    //阻塞函数,调用该函数,线程会阻塞   
		// 当这个函数调用阻塞的时候,会对互斥锁进程解锁,当不阻塞的时候,继续向下执行,会重新加锁
	int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
    //等待多长时间,调用了这个函数,线程会阻塞,直到指定的时间结束

	int pthread_cond_signal(pthread_cond_t *cond);  // 唤醒等待的1个或多个
    // 唤醒1个或多个等待的线程
	int pthread_cond_broadcast(pthread_cond_t *cond);  // 广播 唤醒所有的
    // 唤醒所有的等待的线程

这里的条件变量应用在生产者消费者模型中时,并没有考虑篮子会满的情况;也没有体现出当前生产了多少个产品

当pthread_cond_wait()被唤醒后,必须重新抢夺互斥锁,不能直接执行任务,否则可能发生虚假唤醒。通常的解决办法是在线程被激活后还需要检测等待的条件是否满足

还有一个问题。唤醒丢失

详见下方链接:

https://blog.csdn.net/just_kong/article/details/98871393

2.5 信号量

PV操作

	// 信号量的类型 sem_t
	int sem_init(sem_t *sem, int pshared, unsigned int value);
    /*
      初始化信号量
      参数:
          -sem: 信号量变量的地址
          -pshared: 0用在线程, 非0用在进程
          -value: 信号量中的值 z
     */
  int sem_destroy(sem_t *sem);
     // 释放资源
	int sem_wait(sem_t *sem);
    // 对信号量加锁  调用一次对信号量的值减一, 若信号量的值是0则阻塞  相当于P操作
	int sem_trywait(sem_t *sem);
	int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
	int sem_post(sem_t *sem);
    // 对信号量解锁,调用一次对信号量的值加一   相当于V操作
	int sem_getvalue(sem_t *sem, int *sval);
		// 获取信号量的值 成功返回0,失败返回-1

条件变量和信号量的区别:

  1. 条件变量可以一次唤醒所有等待者,信号量没有这个功能
  2. 信号量始终有一个值(状态的),而条件变量是没有的,没有地方记录唤醒(发送信号)过多少次,也没有地方记录唤醒线程(wait返回)过多少次。从实现上来说一个信号量可以是用mutex + counter + condition variable实现的。因为信号量有一个状态,如果想精准的同步,那么信号量可能会有特殊的地方。信号量可以解决条件变量中存在的唤醒丢失问题。

信号量最有用的场景是用以指明可用资源的数量。

标签:rwlock,int,mutex,pthread,线程,sem
From: https://www.cnblogs.com/Yuqi0/p/17169377.html

相关文章

  • java 如何使用多线程调用类的静态方法?
     1.情景展示静态方法内部实现:将指定内容生成图片格式的二维码;如何通过多线程实现?2.分析之所以采用多线程,是为了节省时间 3.解决方案准备工作logo文件......
  • 一个多线程爬取http://www.infobank.cn的爬虫
    importrequestsfrombs4importBeautifulSoupimportreimportopenpyxlfrommultiprocessing.dummyimportPoolimporttimeimportos#从输入表格获取数据defread_exce......
  • 创建线程有多少种方式
    C++多线程C++11引入了线程类thread,头文件为#include<thread>创建多线程的方法:std::threadthreadName(functionName,parameter1,paramater2,…);传递参数可以传左......
  • 两个线程交替打印一个共享变量
    首先给出基本框架#include<iostream>#include<thread>usingnamespacestd;intmain(){intn=100;inti=0;//创建两个线程threadnewThre......
  • 多线程和多进程的区别
    一个线程从属于一个进程;一个进程可以包含多个线程。一个线程挂掉,对应的进程挂掉,多线程也挂掉;一个进程挂掉,不影响其它进程,多进程稳定。进程系统开销显著大于线程开销;线程......
  • 还不知道线程池的好处?快来了解一下
    摘要:线程池的好处:重用存在的线程,减少对象创建、消亡的开销,性能佳;可以有效控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞。本文分享自华为云社区《......
  • 还不知道线程池的好处?快来了解一下
    摘要:线程池的好处:重用存在的线程,减少对象创建、消亡的开销,性能佳;可以有效控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞。本文分享自华为云社区......
  • jmeter跨线程组调用变量-以token为例
    跨线程组调用变量的解决方法:在beanshell取样器中使用setProperty函数设置全局变量,其他线程组用P函数调用全局变量 跨线程组调用变量的步骤:以token为例跨线程组调用有两......
  • 第8章:多线程
    第8章多线程1、基本概念:程序、进程、线程程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。进程(process):是程序的一次执......
  • Java——四种线程创建方式
    java中创建线程有四种方式,分别是:继承Thread类,重写run方法,然后创建线程对象并调用start方法。实现Runnable接口,实现run方法,然后创建线程对象并传入Runnable实例,再调用start......