多线程和并发之线程

发布于:2025-05-29 ⋅ 阅读:(26) ⋅ 点赞:(0)

线程

前面讲到进程:为了并发执行任务(程序),现代操作系统才引进进程的概念
分析:

  • 创建开销问题:创建一个进程开销:大

    • 子进程需要拷贝父进程的整个地址空间
  • 通信开销问题:进程间的通信

    • 需要用第三方(如:内核
  • P1 -> copy -> 内核 -> copy -> P2

  • 进程间通信代价或者开销也是很大的。进程的地址空间是独立,要通信的话需要用第三方的空

  • 间。

于是,就有人提出能不能在 同一个(同一个进程内部)进程地址空间中进行任务的并发:线程/轻量级
进程

线程是一个比进程更小的活动单位。它是进程中的执行路径(执行分支),线程也是并发的一种形式。
进程内部可以存在多个线程,它并发执行,但是进程内部的所有的线程共享整个进程的地址空间
main 函数:进程的主线程

1 线程特点

  • 创建一个线程要比创建进程开销要小很多

    • 因为创建一个线程的话,不需要拷贝进程的地址空间
  • 实现线程间的通信会更加方便

    • 因为进程内部所有线程共享整个进程地址空间
  • 线程也是一个动态概念:

    • 线程(进程)状态图
      • 就绪态:ready
      • 运行态:running
      • 阻塞态:blocking

有了线程的概念之后

  • 系统的调度单位就从进程变为线程,资源的分配还是以进程为单位

线程是进程内部的一个指令的执行分支,多个线程就是多个指令序列并发执行。这些指令必须在函数
内部,线程的指令部分肯定是封装一个函数的内部的。这个函数,就称之为:线程函数,一个线程在创
建之后,要执行的指令全部封装在该函数内部,这个线程函数执行完毕之后,该线程的任务也就执行完

2 线程函数的原型

typedef void *(*start_routine_t)(void *);
 // 函数指针:指向一个返回值为void*且带有一个void*参数的函数
// 类型重定义:将一个函数指针类型重命名为start_routine_t
 void *my_thread(void *) // 咱自定义的线程函数:必须要符合返回值是void*且带有void*参数的一
//个函数
{
    // 执行线程需要执行的代码
}

3.Linux对线程的API的支持

Linux下采用的是POSIX Thread线程库。简称为:pthread

示例

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
 string str = "你好,线程";
// 线程函数,表示线程启动之后第一时间会执行的函数
void *mythread(void *arg)
{
    cout  << "子线程:"  << str << endl;
    return nullptr;
}

int main()
{
  
    // 创建线程
    // 第一次参数,用来存储线程的id号
    pthread_t tid;
    pthread_create(&tid, nullptr, mythread, nullptr);
    // 子线程先指向父线程再打印 保证子线程在父线程后面结束
    sleep(1);
    cout << "主线程:" << str << endl;
    return 0;
}

3.1 创建一个线程

pthread_create:创建一个线程(启动一个线程)

 PTHREAD_CREATE(3)                                  Linux Programmer's Manual     
                             PTHREAD_CREATE(3)
 NAME
       pthread_create - create a new thread
 SYNOPSIS
       #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:
                线程函数,表示线程启动之后第一时间会执行的函数。
                也就是说新线程创建之后会指向start_routine函数内任务。
            @arg:
                参数,表示新线程要去start_routine执行任务,但是start_routine是一个函数。
start_routine是函数就可以有参数。所有arg实际上就是传给start_routine的参数的。
            @return:
                成功返回0,失败返回-1,同时errno被设置。
    
       */
 /*
 int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *
 (*start_routine) (void *), void *arg)
 {
    // 其他的代码
    // 创建线程去执行start_routine
    start_routine(arg);
    
    如果线程创建成功
    return 0;
    
    否则就是:
    return -1;
 }
 */

示例

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
 string str = "你好,线程";
// 线程函数,表示线程启动之后第一时间会执行的函数
void *mythread(void *arg)
{
    cout  << "子线程:"  << str << endl;
    return nullptr;
}

int main()
{
  
    // 创建线程
    // 第一次参数,用来存储线程的id号
    pthread_t tid;
    pthread_create(&tid, nullptr, mythread, nullptr);
    // 子线程先指向父线程再打印 保证子线程在父线程后面结束
    sleep(1);
    cout << "主线程:" << str << endl;
    return 0;
}

3.2 线程的退出

线程函数的退出(线程函数的返回)

 void *start_routine(void *arg)
 {
    return nullptr; // 线程结束
}

在线程执行的任意时刻调用pthread_exit

 PTHREAD_EXIT(3)                                    Linux Programmer's Manual     
                               PTHREAD_EXIT(3)
 NAME
       pthread_exit - terminate calling thread
 SYNOPSIS
       #include <pthread.h>
       void pthread_exit(void *retval);
       /*
            @描述
                立即结束线程
            @retval:
                线程结束之后需要返回的参数,返回值的指针。
       */

被别人干掉

  • cancel:被别人取消(其他线程调用pthread_cancel)

    • t1:pthread_cancel(t2)

    • t1调用取消函数,取消t2,t2不一定会被取消

      • 因为t2能不能被其他线程取消,取决于t2线程的一个属性:取消属性

      • 它是否可以被cancelled

 PTHREAD_CANCEL(3)                                  Linux Programmer's Manual     
                             PTHREAD_CANCEL(3)
 NAMESYNOPSIS
       #include <pthread.h>
       int pthread_cancel(pthread_t thread);
       /*
            @描述:
                取消一个指定的线程
            @thread:
                线程号,需要取消的那个线程的id号
            @return:
                成功返回0,失败返回非0.
       */
       pthread_cancel - send a cancellation request to a thread
SYNOPSIS
       #include <pthread.h>
       int pthread_cancel(pthread_t thread);
       /*
            @描述:
                取消一个指定的线程
            @thread:
                线程号,需要取消的那个线程的id号
            @return:
                成功返回0,失败返回非0.
       */
  • 这个属性叫做:可以被取消属性
    • PTHREAD_CANCEL_ENABLE:表示该线程可以被取消
    • PTHREAD_CANCEL_DISABLE:表示该线程不能被取消
 PTHREAD_SETCANCELSTATE(3)                          Linux Programmer's Manual     
                     PTHREAD_SETCANCELSTATE(3)
 NAME
       pthread_setcancelstate, pthread_setcanceltype - set cancelability state 
and type
 SYNOPSIS
       #include <pthread.h>
       int pthread_setcancelstate(int state, int *oldstate);
       /*
            @描述:
                设置线程取消属性的
            @state: 
                设置线程的取消状态
                    PTHREAD_CANCEL_ENABLE:表示该线程可以被取消
                    PTHREAD_CANCEL_DISABLE:表示该线程不能被取消
            @oldstate:
                线程上一次的取消状态
            @return:
                成功返回0,失败返回其他值
       */  

一个线程退出了,并不是所有资源都会释放。一个线程的退出,它资源释放全部被释放,取决于一个属 性

示例

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
 string str = "你好,线程";
// 线程函数,表示线程启动之后第一时间会执行的函数
void *mythread(void *arg)
{
    //  存储的是调用前的状态
    int old_state = 0;
    // 控制当前线程的可取消性 PTHREAD_CANCEL_DISABLE不能被取消 |PTHREAD_CANCEL_ENABLE可以被取消
    // pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,& old_state);
    cout << (old_state == PTHREAD_CANCEL_DISABLE? "不可以被取消":"可以被取消") << endl;
    while (1)
    {
        cout  << "子线程:"  << str << endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
  
    // 创建线程
    // 第一次参数,用来存储线程的id号
    pthread_t tid;
    pthread_create(&tid, nullptr, mythread, nullptr);
    // 子线程先指向父线程再打印 保证子线程在父线程后面结束
    sleep(3);
    cout << "主线程:" << str << endl;
    pthread_cancel(tid);
     sleep(3);
    return 0;
}

3.3 资源分离

detach:分离属性:

  • ENABLE: 分离资源属性

    • 该线程结束,它的所有资源都会自动释放。
  • DISABLE: 不分离资源

    • 该线程结束,会有部分资源不会自动释放,需要其他线程调用pthread_join这个函数才能完

    全释放

3.3.1 线程资源回收函数
PTHREAD_JOIN(3)                                                   Linux 
Programmer's Manual                                                   
PTHREAD_JOIN(3)
 NAME
       pthread_join - join with a terminated thread
 SYNOPSIS
       #include <pthread.h>
       int pthread_join(pthread_t thread, void **retval);
       /*
            @描述:
                等待一个指定的线程结束 阻塞状态
            @thread:
                需要等待的线程id号
            @retval:
                二级指针,表示线程函数的返回值指针
            @return:
                成功返回0
                失败返回-1
       */
3.3.2 资源分类属性
 PTHREAD_DETACH(3)                                                 Linux 
Programmer's Manual                                                 
PTHREAD_DETACH(3)
 NAME
       pthread_detach - detach a thread
 SYNOPSIS
       #include <pthread.h>
       int pthread_detach(pthread_t thread);
       /*
            @描述:
                设置线程的资源分离属性
            @thread:
                需要设置资源分离属性的那个线程id
            @return:
                成功返回0
                失败返回-1
         */
3.3.3 获取自身ID号
#include <pthread.h>
 pthread_t pthread_self(void);
 /*
作用:
获取当前所在线程的tid号
@return:
返回当前所在线程的tid号
*/

示例

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
 string str = "你好,线程";
// 线程函数,表示线程启动之后第一时间会执行的函数
void *mythread(void *arg)
{
   pthread_detach(pthread_self());
    while (1)
    {
        cout  << "子线程:"  << str << endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
  
    // 创建线程
    // 第一次参数,用来存储线程的id号
    pthread_t tid;
    pthread_create(&tid, nullptr, mythread, nullptr);
    
    cout << "主线程:" << str << endl;
    // 取消线程   
    pthread_cancel(tid);
    sleep(3);
    // 资源回收
    pthread_join(tid,nullptr);
    return 0;
}

4. 线程的同步/互斥机制

为了线程之间,能够去有序的访问共享资源,引用信号量机制

  • 信号量System VPOSIX 信号量
  • 线程互斥锁

4.1 线程互斥锁

线程互斥锁也是信号量,只不过线程互斥锁,存在于进程地址空间,用于线程间同步和互斥操作,线程
互斥锁它的效率相对信号量来说要高。
线程互斥锁:使用pthread_mutex_t 的类型来描述一个锁
安装线程POSIX 帮助手册:sudo apt-get install manpages-posix-dev // 安装posix 帮助手册

  • 初始化线程互斥锁
PTHREAD_MUTEX_INIT(3POSIX)                                             POSIX Programmer's 
Manual                             					PTHREAD_MUTEX_INIT(3POSIX)

 PROLOG
		 This  manual  page  is part of the POSIX Programmer's Manual.  The Linux 
implementation of this interface may differ (consult the
         corresponding Linux manual page for details of Linux behavior), or the 
interface may not be implemented on Linux.
 NAME
 		pthread_mutex_init — destroy and initialize a mutex
 SYNOPSIS
 		#include <pthread.h>
 		int pthread_mutex_init(pthread_mutex_t *restrict mutex,const 
pthread_mutexattr_t *restrict attr);
 /*
 		@描述:
			初始一个线程互斥锁
		@mutex:
			需要初始化的线程互斥锁地址
			
			pthread_mutex_t mutex; // 创建了一个互斥锁	
  			&mutex ---> 互斥锁地址
                
         @attr:
             线程互斥锁的属性,一般为NULL,采用默认属性
             如:
                  线程的互斥锁默认设置 1 unlock
                    
            @return:
                成功返回0,失败返回-1
       */
  • 线程互斥锁的PV操作
int pthread_mutex_lock(pthread_mutex_t *mutex);
 /*
    作用:
        死等上锁,如果该锁没有被释放,则会一直阻塞在此函数,等待该锁被释放。
    @mutex:
        需要上锁的互斥锁指针
    @return:
        成功返回0,表示获取到了该互斥锁
        返回-1,表示获取出错,没有获取到互斥锁
*/
 int pthread_mutex_trylock(pthread_mutex_t *mutex);
 /*
    作用: 
        尝试上锁,尝试性上锁,如果该锁没被释放,那么立即返回执行后面的代码。
    @mutex:
        需要上锁的互斥锁指针
    @return:
        成功返回0,表示获取到了该互斥锁
        其他值,表示没有获取到互斥锁
*/
 int pthread_mutex_timedlock(pthread_mutex_t *mutex,struct timespec 
*abs_timeout);
*abs_timeout);
 /*
    作用:
        限时上锁,如果该锁没有被释放,则会一直阻塞在此函数中一段实际,等待该锁被释放。如果时间
过了还没有被释放,那么果断放弃执行后面的代码。
        
    @mutex:
        需要上锁的互斥锁指针
    @abs_timeout:
        绝对时间(超时时间),上锁的时间范围。
    @return:
        成功返回0,表示获取到了该互斥锁
        其他值,表示没有获取到互斥锁
        
*/
  • V操作(解锁操作)
int pthread_mutex_unlock(pthread_mutex_t *mutex);
 /*
作用:
解锁线程互斥锁
@mutex:
需要解锁的线程互斥锁指针
*/
  • 线程互斥锁的销毁操作
int pthread_mutex_destroy(pthread_mutex_t *mutex);
 /*
	作用:
		销毁一个线程互斥锁
	@mutex:
		需要销毁的线程互斥锁指针
	@return:
		成功返回0,失败返回-1
 */

5.生产者消费者模型

生产者消费者模型:利用厂商和消费者关系,由生产者线程进行生产(产生任务),再由消费者线程消 费(执行任务),没有任务的时候,消费者等待生产者产生任务。

  • 共享资源的互斥访问问题
    • 信号量/线程互斥锁

当缓冲区(生产者没有产出的时候)没有数据的时候,(消费者)应该怎么办?

  • 不停的去测试,看有没有数据。

    • 轮询访问,但是轮询有缺陷:一直在访问,浪费CPU资源。轮询有时间差,占用总线:`Is ``

      always busy

  • 让出CPU,当有数据的时候,再唤醒我(wake up),线程条件变量:同步

5.1 线程条件变量

线程条件变量:在多线程程序设计中,可以用条件变量为表示一个特定的条件或者是事件
pthread_cond_t :来描述一个条件变量(类型)
至于条件变量,到底是一个什么事件或者说表示一个什么条件?完全由程序猿去解释这个条件变量所代
表的含义。
在条件变量上的三种操作:

  • 初始化
  • 等待一个条件变量(等待该条件变量所表示的事件)
  • 唤醒一个线程/触发条件变量(唤醒了正在等待该事件的线程)
int data = 0;
 main : 主线程:生产者:包工头
	data = 1;
 t1:子线程:消费者:牛马
	when data == 1;
		干活
		data = 0; 

5.2 线程条件变量 API

5.2.1 初始化/销毁条件变量
PTHREAD_COND_DESTROY(3POSIX)                                      		POSIX 
Programmer's Manual                                      
PTHREAD_COND_DESTROY(3POSIX)
		
 PROLOG
 		This  manual  page  is  part of the POSIX Programmer's Manual.  The Linux 
implementation of this interface may differ (consult the corresponding Linux
 		manual page for details of Linux behavior), or the interface may not be 
implemented on Linux.
 NAME
 		pthread_cond_destroy, pthread_cond_init — destroy and initialize 
condition variables
 
SYNOPSIS
 		#include <pthread.h>
 		
 		int pthread_cond_destroy(pthread_cond_t *cond);
 /*
			 @描述:
				销毁一个条件变量
			@cond:
				需要销毁条件变量指针
*/
  int pthread_cond_init(pthread_cond_t *restrict cond,const 
pthread_condattr_t *restrict attr);
        /*
            @描述:
                初始化一个条件变量
            @cond:
                需要初始化的条件变量的指针
            @attr:
                初始化的条件变量的属性,一般为NULL,采用默认设置
            @return:
                成功返回0,失败返回其他值
        
        */
5.2.2 等待一个条件变量
 PTHREAD_COND_TIMEDWAIT(3POSIX)                                    POSIX 
Programmer's Manual                                    
PTHREAD_COND_TIMEDWAIT(3POSIX)
 PROLOG
       This  manual  page  is  part of the POSIX Programmer's Manual.  The Linux 
implementation of this interface may differ (consult the corresponding Linux
       manual page for details of Linux behavior), or the interface may not be 
implemented on Linux.
 NAME
       pthread_cond_timedwait, pthread_cond_wait — wait on a condition
 SYNOPSIS
       #include <pthread.h>
       int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t 
*restrict mutex,
           const struct timespec *restrict abstime);
       /*
            @描述:
                限时等待条件变量
            @cond:
                需要等待的那个条件变量指针
            @mutex:
                线程互斥锁:为了保护cond所表示的那个事件/共享资源的。
                条件变量实际也是一个共享资源。
            @abstime:
                绝对时间,需要被唤醒的绝对时间
       */
           
       int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t 
*restrict mutex);
        /*
            @描述:
                等待那个条件变量
            @cond:
                需要等待的那个条件变量指针
            @mutex:
                线程互斥锁:为了保护cond所表示的那个事件/共享资源的。
                条件变量实际也是一个共享资源。
            @abstime:
                绝对时间,需要被唤醒的绝对时间
                  */
           
       int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t 
*restrict mutex);
        /*
            @描述:
                等待那个条件变量
            @cond:
                需要等待的那个条件变量指针
            @mutex:
                线程互斥锁:为了保护cond所表示的那个事件/共享资源的。
                条件变量实际也是一个共享资源。
                  mutex:locked 上锁
                pthread_cond_wait()
                {
                    ... 准备工作
                    mutex:unlock 解锁
                    让出CPU 等待
                    ...
                    when 当条件产生的时候,其他的线程唤醒我的时候
                        mutex:locked 上锁
                }
            @return:
                成功返回0,被其他线程唤醒
                失败返回其他值
        
        */
5.3.3 唤醒线程/触发条件变量
PTHREAD_COND_BROADCAST(3POSIX)                                    POSIX 
Programmer's Manual                                    
PTHREAD_COND_BROADCAST(3POSIX)
 PROLOG
       This  manual  page  is  part of the POSIX Programmer's Manual.  The Linux 
implementation of this interface may differ (consult the corresponding Linux
       manual page for details of Linux behavior), or the interface may not be 
implemented on Linux.
 NAME
       pthread_cond_broadcast, pthread_cond_signal — broadcast or signal a 
condition
 SYNOPSIS
       #include <pthread.h>
        // 广播唤醒
       int pthread_cond_broadcast(pthread_cond_t *cond);
       /*
            @描述:
                唤醒所有正在等待的线程
            @cond:
                那个条件变量
            @return:
                成功返回0,失败返回其他值
       
       */
       // 单个唤醒
       int pthread_cond_signal(pthread_cond_t *cond);
       /*
            @描述:
                只唤醒一个线程在等待的线程。
            @cond:
                那个条件变量
            @return:
                成功返回0,失败返回其他值
                */

注意:广播唤醒和单个唤醒的区别

  • 广播唤醒:唤醒所有等待的线程,去执行任务,但是任务可能不够分,那么没分到的线程继续休眠
  • 单个唤醒:随机唤醒一个线程执行任务,其他线程继续休眠。
#include <iostream>
#include <vector>
#include <unistd.h>
#include <pthread.h>

// 全局变量供父子线程使用
int a = 0;
int b = 0;
// 线程互斥锁
pthread_mutex_t mutex;
// 条件变量
pthread_cond_t cond;
void *myrhtread(void *arg)
{
    usleep(10);
    std::cout << "子线程:" << pthread_self() << "启动完成" << std::endl;
    while (1)
    {
           // 上锁
        pthread_mutex_lock(&mutex);
        // 休息,等待主线程给活,没有就休息等待
        pthread_cond_wait(&cond, &mutex);
     
         //
        if (a < 0 && b < 0)
        {
            //小于0时候退先解锁在退出
            pthread_mutex_unlock(&mutex);
            break;
        }
        std::cout << "子线程:" << pthread_self()  << "计算结果是:" << a + b << std::endl;
        // 解锁
        pthread_mutex_unlock(&mutex);
    }
  return nullptr;
}
int main()
{
    // 初始化互斥锁
    pthread_mutex_init(&mutex, nullptr);
    // 初始化条件变量
    pthread_cond_init(&cond, nullptr);
    // 定义五个工作者(牛马)
    std::vector<pthread_t> works;
    // 创建线程
    for (int i = 0; i < 5; i++)
    {
        pthread_t tid;
        pthread_create(&tid, nullptr, myrhtread, nullptr);
        // 添加到容器里面
        works.push_back(tid);
    }
        usleep(10);
    // 生产者
    while (1)
    {
        // 上锁
        pthread_mutex_lock(&mutex);
        
        std::cout << "请输入" << std::endl;
        std::cin >> a >> b;
        // 退出条件
        if(a < 0 && b < 0)
        {
            pthread_mutex_unlock(&mutex);
            break;
        }
        // 解锁
        pthread_mutex_unlock(&mutex);
       
       // 唤醒线程可以工作了
        pthread_cond_signal(&cond);
        usleep(10);
    }
    // 唤醒全部线程逐步退出
    pthread_cond_broadcast(&cond);

     // 等待工作者线程结束
    for(pthread_t tid:works)
    {
        pthread_join(tid, nullptr);
    }
    // 销毁条件变量
    pthread_cond_destroy(&cond);
    // 销毁锁
    pthread_mutex_destroy(&mutex);
}

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到