Linux多线程

Linux C++ 多线程实战一——多线程排序     由于最近在学习C++从底层(指针,对象模型,内存管理)再到网络socket编程,多线程编程,数据库编程等方面知识,需要从Java慢慢的迁移过来。这是以前学习Java的学习路线,所以就把原来Java的做的项目全部用C++重新实现一遍。1. 多线程基础    这个可以看我的博客,里面都有详细的介绍以及如何使用linux C++多线程编程,参考如下:    Linux C++ 多线程编程基础(汇... 阅读详情

 


待我慢慢总结...

又是一个周末呀,明天又要开始码论文了,真是毫无意义......,这群老师真是浪费国家科研经费....


参考文章:

[1] 线程与进程,多线程,多进程,线程并发,线程并行

[2] 《嵌入式Linux开发教程(上册)》


1、多线程概述

线程(thread)是包含在进程内部的顺序执行,是进程中的实际运作单位,也就是系统能够调度的最小单位。一个进程中可以并发多条线程,每条线程并发执行不同任务。

  • 并发

 并发是把CPU运行时间划分成若干个时间段,每个时间段再分配给各个线程执行,当一个线程在运行时,其它线程处于挂起状。从宏观角度是同时进行的,但从微观角度并不是同时进行。

     CPU根据线程调度算法来切换线程。当正在执行的一个线程需要进行IO操作或者需要访问内存的时候,CPU完全可以放弃该线程,转而调度线程就绪队列上的其他线程,被放弃的线程则进入阻塞状态,IO操作或者访问内存操作结束之后,该线程可以进入线程就绪队列上。

     典型的线程调度算法:(1) FIFO算法。在非抢占式系统中,所有的线程构成一个先进先出队列,最先进入队列的线程获得CPU,等到放弃处CPU时,又回到队列尾部,下一个线程继续执行。若有新的线程进来,则添加到队列尾部。(2) 时间片轮转调度算法。(3) 优先级调度算法。

 

  • 并行

并行是同一时刻当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,是真正意义上的不同线程在同一时刻同时执行。

引入线程的好处:

  1. 线程占用资源要比进程少的多
  2. 创建一个新的线程花费的代价小
  3. 切换线程方便
  4. 提高并发性

多线程举例:比如用浏览器,同时进行浏览网页、播放视频、下载资源、听音乐等操作

多线程缺点: 

  1. 多线程比多进程成本低,不过性能也更低
  2. 一个线程的崩溃可能影响到整个程序的稳定性
  3. 线程多了之后,线程本身的调度也麻烦,需要消耗较多的CPU
  4. 无法直接获取系统的资源,总体能够达到的性能上限有限制
  5. 线程之间的同步和加锁控制比较麻烦

同一进程的线程共享哪些资源?

  • 堆:由于堆是在进程空间中开辟出来的,所以它是理所当然地被共享的;因此new出来的都是共享的(16位平台上分全局堆和局部堆,局部堆是独享的)。
  • 全局变量:它是与具体某一函数无关的,所以也与特定线程无关;因此也是共享的。
  • 局部静态变量:虽然对于局部变量来说,它在代码中是“放”在某一函数中的,但是其存放位置和全局变量一样,存于堆中开辟的.bss和.data段,是共享的。
  • 文件等公用资源:这个是共享的,使用这些公共资源的线程必须同步。Win32 提供了几种同步资源的方式,包括信号、临界区、事件和互斥体。

线程独享的资源?

  •  栈:栈是独享的
  • 寄存器:这个可能会误解,因为电脑的寄存器是物理的,每个线程去取值难道不一样吗?其实线程里存放的是副本,包括程序计数器PC
  • 线程ID

线程与进程的关系:

  • 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有个主线程;
  • 资源分配给进程,同一进程的所有线程共享该进程的所有资源;
  • 线程作为调度和分配的基本单位,进程作为拥有资源的基本单位;
  • 进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源;
  • 在创建或者撤销进程时,由于系统要为之分配和回收资源,因此导致系统的开销大于创建或撤销线程时的开销。

为什么需要多线程?

  • 方便通信和数据交流
    • 对于进程来说,他们具有独立的数据空间,要进行数据的穿肚只能通过通信的方式进行,这种方式费时且不便;
    • 同一进程下的线程共享数据空间,所以一个线程的数据可以直接为其他线程所使用,快捷且方便。
  • 更高效地利用CPU
    • 对图形界面的程序尤其有意义,当一个操作耗时很航,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作。而使用多线程技术,将耗时长的操作置于一个新的线程,就可以避免这种尴尬的情况
    • 同时,多线程使用CPU系统更加高效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。(并行)

2、POSIX Threads概述

POSIX Threads(通常称为Pthreads)定义了创建和操作线程的一套API接口,一般用于UNIX-like POSIX系统中(如FreeBSD、GNU\Linu、OpenBSD、Mac Os等系统)。

Pthreads接口可以根据功能划分为四个组:

  • 线程管理
  • 互斥量
  • 条件变量
  • 同步

编写Pthreads多线程程序时,源码只要包含pthread.h头文件就可以使用Pthreads库中的所有类型和函数:

#incldue <pthread.h>

在编译Pthreads程序时在编译和链接过程中需要加上-pthread参数。

3、线程管理

线程管理包含了线程的创建、终止、等待、分离、设置属性等操作。

3.1 线程ID

线程ID可以看作线程的句柄,用来引用一个线程。

Pthreads线程有一个pthread_t类型的ID,线程可以用过pthread_self()函数来获取自己的ID。pthread_self()函数原型如下:

pthread_t pthread_self(void);

比较两个线程ID是否相等函数:(因为pthread_t可能是结构体)

int pthread_equal(pthread_t t1,pthread_t t2);

如果t1等于t2,则该函数返回一个非0值,否则返回0.

3.2 线程创建和终止

3.2.1 线程创建

在进程中创建一个新线程的函数是:(线程创建后会立即运行)

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

返回值说明:

如果函数成功返回0,否则返回一个非0的错误码

参数说明:

  • thread:新创建的线程ID
  • attr:表示一个封住了线程的各种属性的属性对象,如果attr为NULL,则新线程使用默认的属性
  • start_routine:线程开始执行时调用的函数名字,start_routine函数有一个指向void的指针参数,并由pthread_create第四个参数arg指定值,同时start_routine函数返回一个指向void的指针,这个值被pthread_join当做退出状态处理
  • arg为参数start_routine指定函数的参数

3.2.2 线程终止

  • 终止线程可以直接调用exit()、执行main()中的return或者通过进程的某个其他线程调用exit()来实现。所有线程都会终止
  • 调用exit()会使整个进程终止;
  • 调用pthread_exit()(或者说是pthread_eixt(NULL))只会使调用的线程终止,如果进程的最后一个线程调用了pthread_exit(),则进程会带着状态返回值0退出
    • void pthread_exit(void * retval);
      • retval 是一个void类型的指针,可以将线程的返回值当做pthread_exit()的参数传入

关于如何获取Linux系统当前时间:Linux编程--获取当前时间、 Linux 下c获取当前时间(精确到秒和毫秒或者微秒)

#include <stdio.h>
#include <pthread.h>
#include <sys/time.h>
/*线程1:函数*/
int number = 0;
void saythread1(){
        time_t timep;
        time(&timep);

        struct timeval tv;
        gettimeofday(&tv, NULL);
        number++;
        printf("======================\n");
        printf("time is %s",ctime(&timep));
        printf("microsecond = %ld\n",tv.tv_sec*1000000 + tv.tv_usec);
        printf("i am thread 1 fuction!\n");
        printf("thread1's number = %d\n",number);

        pthread_exit(NULL);
}
/*线程2:函数*/
void saythread2(){
        number++;
        time_t timep;
        time(&timep);

        struct timeval tv;
        gettimeofday(&tv, NULL);
        printf("======================\n");
        printf("time is %s",ctime(&timep));
        printf("microsecond = %ld\n",tv.tv_sec*1000000 + tv.tv_usec);
        printf("i am thread 1 fuction!\n");
        printf("i am thread 2 fuction!\n");
        printf("thread2's number = %d\n",number);
        pthread_exit(NULL);
}
int main(char argc,char **argv){
        int ret1,ret2;
        pthread_t thread1,thread2;
        printf("this is a thread test!\n");
        /* 创建线程1 */
        //使用(void*)saythread1也可以正常编译,输出,为什么?
        ret1 = pthread_create(&thread1,NULL,(void*)saythread1,(void*)NULL);
        if(ret1 != 0)
                printf("thread1 create error!\n");
        /* 创建线程2 */
        printf("number = %d\n",number);
        ret2 = pthread_create(&thread2,NULL,(void*)saythread2,(void*)NULL);
        if(ret2 != 0)
                printf("thread2 create error!\n");
        printf("main thread exit!\n");
        pthread_exit(NULL);
        return 0;
}

编译:

gcc -o pthreadtest1 pthreadtest1.c -pthread

不加-pthread进行编译会发生错误:

输出:

this is a thread test!
number = 0
main thread exit!
======================
time is Sun May 26 21:40:38 2019
microsecond = 1558878038112226
i am thread 1 fuction!
i am thread 2 fuction!
thread2's number = 1
======================
time is Sun May 26 21:40:38 2019
microsecond = 1558878038115158
i am thread 1 fuction!
thread1's number = 2

以上程序看似是先执行完主线程、然后执行线程2,最后执行线程1.这是因为每个线程执行时间小于系统调度的最小时间片,所以导致线程没有并发执行,只需要在printf后面加一个延时函数,人为增加线程执行所需时间就能看到系统对线程之间的调度

#include <stdio.h>
#include <pthread.h>
#include <sys/time.h>
#include <unistd.h>
/*线程1:函数*/
int number = 0;
void saythread1(){
        time_t timep;
        time(&timep);
        struct timeval tv;
        gettimeofday(&tv, NULL);
        number++;
        printf("time is %s",ctime(&timep));
        sleep(1);
        printf("microsecond = %ld\n",tv.tv_sec*1000000 + tv.tv_usec);
        sleep(1);
        printf("i am thread 1 fuction!\n");
        sleep(1);
        printf("thread1's number = %d\n",number);

        pthread_exit(NULL);
}
/*线程2:函数*/
void saythread2(){
        number++;
        time_t timep;
        time(&timep);
        struct timeval tv;
        gettimeofday(&tv, NULL);
        printf("time is %s",ctime(&timep));
        sleep(1);
        printf("microsecond = %ld\n",tv.tv_sec*1000000 + tv.tv_usec);
        sleep(1);
        printf("i am thread 2 fuction!\n");
        sleep(1);
        printf("thread2's number = %d\n",number);
        pthread_exit(NULL);
}
int main(char argc,char **argv){
        int ret1,ret2;
        pthread_t thread1,thread2;
        printf("this is a thread test!\n");
        /* 创建线程1 */
        ret1 = pthread_create(&thread1,NULL,(void*)saythread1,(void*)NULL);
        if(ret1 != 0)
                printf("thread1 create error!\n");
        /* 创建线程2 */
        printf("number = %d\n",number);
        ret2 = pthread_create(&thread2,NULL,(void*)saythread2,(void*)NULL);
        if(ret2 != 0)
                printf("thread2 create error!\n");
        printf("main thread exit!\n");
        pthread_exit(NULL);
        return 0;
}

输出:

注意:由于操作系统线程调度的随机性,多线程范例程序实际执行结果可能与本案例有所不同。

3.3 连接与分离

线程可以分为分离线程(DETACHED)和非分离线程(JOINABLE)两种:

  • 分离线程:指程序退出时会释放其资源的线程;
  • 非分离线程:退出后不会马上释放资源,需要另一个线程为其调用pthread_join函数或者进程退出时才会释放资源。

只有非分离线程才是可连接的,分离线程退出时不汇报告它的退出状态。

3.3.1 线程分离

pthread_detach()函数可以将非分离线程设置为分离线程,其函数原型如下:

pthread_detach(pthread_t thread)
  • 参数thread是要设置为分离的线程ID。
  • 函数成功返回0,否则返回一个非0的错误码

线程可以由其他线程来设置分离,也可以由自己设置分离(使用pthread_detach(pthread_self()))

3.3.2 线程连接

如果一个线程是非分离的线程,那么其他线程可以调用pthread_detach()函数对其进行连接,pthread_join()函数原型如下:

int pthread_join(pthread_t thread, void * retval);
  • pthread_join()函数将调用的线程挂起,直到参数thread指定目标线程终止运行为止。
  • 参数retval为指向线程返回值的指针提供一个位置,这个返回值是目标线程通用pthread_exit()或者return所提供的值。当目标线程无需返回的时候可以使用NULL值,调用线程如果不需要对目标线程的返回状态进行检查可直接将retval幅值为NULL。
  • 如果pthread_join()成功调用,函数返回0,如果不成功,返回一个错误码
  • 调用该函数的线程会一直阻塞,直到指定的tid线程退出,如果这个指定的线程通过return返回或pthread_exit()退出,那么这个线程的退出码会被保存到第二个参数rval里面。如果这个线程是被取消的,这时就没有退出码,rval就会被置为PTHREAD_CANCELED。
  • 调用pthread_join()会是指定的线程处于分离状态,如果指定的线程已经处于分离状态就会调用失败。
#include <stdio.h>
#include <pthread.h>
#include <sys/time.h>
#include <unistd.h>
/*线程1:函数*/
int number = 0;
void saythread1(){
        time_t timep;
        time(&timep);
        struct timeval tv;
        gettimeofday(&tv, NULL);
        number++;
        printf("time is %s",ctime(&timep));
        sleep(1);
        printf("microsecond = %ld\n",tv.tv_sec*1000000 + tv.tv_usec);
        sleep(1);
        printf("i am thread 1 fuction!\n");
        sleep(1);
        printf("thread1's number = %d\n",number);

        pthread_exit(NULL);
}
/*线程2:函数*/
void saythread2(){
        number++;
        time_t timep;
        time(&timep);
        struct timeval tv;
        gettimeofday(&tv, NULL);
        printf("time is %s",ctime(&timep));
        sleep(1);
        printf("microsecond = %ld\n",tv.tv_sec*1000000 + tv.tv_usec);
        sleep(1);
        printf("i am thread 2 fuction!\n");
        sleep(1);
        printf("thread2's number = %d\n",number);
        pthread_exit(NULL);
}
int main(char argc,char **argv){
        int ret1,ret2;
        pthread_t thread1,thread2;
        printf("this is a thread test!\n");
        /* 创建线程1 */
        ret1 = pthread_create(&thread1,NULL,(void*)saythread1,(void*)NULL);
        if(ret1 != 0)
                printf("thread1 create error!\n");
        /* 创建线程2 */
        printf("number = %d\n",number);
        ret2 = pthread_create(&thread2,NULL,(void*)saythread2,(void*)NULL);
        if(ret2 != 0)
                printf("thread2 create error!\n");
        /*****************************************************
        ******************************************************
         * 等待线程1 结束
        ******************************************************
        ******************************************************/
        pthread_join(thread1,NULL);         
        printf("main thread exit!\n");
        pthread_exit(NULL);
        return 0;
}

 

主线程等待线程1结束运行,待线程1结束运行之后,主线程继续运行。

注意:为了防止内存泄露,长时间运行的程序最终应该为每一个线程调用pthread_detach()或者pthread_join()

3.4 线程属性

线程的属性包括:

  • 栈大小
  • 调度策略
  • 线程状态

通常先创建一个属性对象,然后在属性对象上设置属性的值,再将属性对象传给pthread_create函数的第二个参数用来创建含有该属性的线程。

一个属性对象可以多次传给pthread_create()函数,以创建多个含有相同属性的线程

3.4.1 属性对象

(1)初始化属性对象

首先看一下线程创建函数:

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

线程属性参数是:const pthread_attr_t * attr

通常,我们使用pthread_attr_init()函数来将属性对象使用默认值进行初始化,该函数原型如下:

int pthread_attr_init(pthread_attr_t *attr);

函数只有一个参数,是一个指向pthread_attr_t的属性对象的指针。

函数成功返回0,否则返回一个非零的错误码.

(2)销毁属性对象

销毁属性对象使用pthread_attr_destroy()函数,函数的原型是:

int pthread_attr_destroy(pthread_arrt_t *attr);

函数只有一个参数,是一个指向pthread_attr_t的属性对象的指针。

函数成功返回0,否则返回一个非零的错误码.

3.4.2 线程状态

线程具有两种状态,分别是:

  • PTHREAD_CREATE_JOINABLE:非分离线程
  • PTHREAD_CREATE_DETACHED:分离线程

(1)获取线程状态

获取线程状态的函数一般是pthread_attr_getdetachstate(),其原型如下:

int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
  • attr是一个指向已初始化对象的指针;
  • detachstate是所获状态值的指针。

函数成功返回0,失败返回一个非0的错去码。

(2)设置线程状态

用来获取线程状态的函数是:pthread_attr_setdetachstate(),其原型如下:

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
  • attr是一个指向已初始化对象的指针;
  • detachstate是要设置的值。

函数成功返回0,失败返回一个非0的错去码。

3.4.3 线程栈

[1] 栈的作用

每个线程都有一个独立的调用栈,线程的栈大小在线程创建的时候就已经固定下来了。Linux系统线程默认栈的大小为8MB,只有主线程的栈大小会在运行过程中自动增长,用户可以通过属性对象来设置和获取栈的大小。

(1)获取线程栈

获取线程栈大小的函数是:pthread_atttr_getstacksize(),原型如下:

int pthread_attr_getstachsize(pthread_attr_t *attr, size_t *stacksize)
  • attr是一个指向已初始化对象的指针;
  • stacksize是所获栈大小的指针。

函数成功返回0,失败返回一个非0的错去码。

(2)设置线程栈

设置线程栈大小的函数是:pthread_atttr_setstacksize(),原型如下:

int pthread_attr_setstachsize(pthread_attr_t *attr, size_t *stacksize)
  • attr是一个指向已初始化对象的指针;
  • stacksize是需要设置栈的大小。

函数成功返回0,失败返回一个非0的错去码。

 

4、线程安全

        多线程编程环境中,多个线程同时调用 某些函数可能会产生错误的结果,这些函数成为非线程安全函数。如果库函数能够在多个线程中同时执行并且互不干扰,那么这些库函数就是线程安全(thread_safe)函数。

POSIX.1-2008规定除了下表中的特定函数以外,所有标准库的函数都应该是线程安全函数。有些库函数虽然不是线程安全函数,但系统有后最为_r的线程安全版本,如strtok_r。

 

5、互斥量

5.1 临界区

在计算机系统中有许多共享资源不允许用户并行使用,例如打印设备,如果它同时打印两份文件,就会产生交错,从而无法获得正确的文档。像打印机这样额共享设备被称为排它性资源,因为他一次只能有一个执行流访问。执行流必须以互斥的方式执行访问排它性资源的代码。

临界区是必须以互斥方式执行的代码段,也就是说,在临界区的范围内只能有一个活动的执行线程。

5.2 什么是互斥量

互斥量(Mutex)又称为互斥锁,是一种用来保护临界区的特殊变量,它可以处于锁定状态,也可以处于解锁状态:

  • 如果互斥锁是锁定的,就是某个线程正持有这个互斥锁;
  • 如果没有线程持有这个互斥锁,那么这个互斥锁就处于解锁状态

每个互斥锁内部都有一个线程等待队列,用来保存等待该互斥锁的线程。

  • 当互斥锁出于解锁状态,一个线程试图获取这个互斥锁时,这个线程就可以得到这个互斥锁而不会阻塞
  • 当互斥锁出于锁定状态,一个线程试图获取这个互斥锁时,这个线程将阻塞在互斥锁的等待队列

互斥量有最简单、最有效的线程同步机制。程序可以用它来保护临界区,以获得对排它性资源的访问权。另外,互斥量只能被短时间的持有,使用完临界资源后应该立即释放锁。

5.3 互斥量的创建和销毁

5.3.1 创建互斥量

pthread使用pthread_mutex_t类型的变量来表示互斥量,同时在使用互斥量进行通不值钱需要对它进行初始化,可以使用静态方式或者动态方式对互斥量队形初始化。

(1)静态方式:使用PTHREAD_MUTEX_INITIALIZER

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

(2)动态方式

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
  • 参数mutex是一个要初始化的互斥量指针;
  • 参数attr传递NULL来初始化一个带有默认属性的互斥量,否则就要用类似于线程属性对象所使用的方法,先创建互斥量属性对象,再用该属性对象来创建互斥量

函数成功返回0,否则返回一个非0的错误码。

静态初始化程序往往比调用pthread_mutex_init()函数更加有效,而且在任何线程开始执行之前,都要确保互斥锁被初始一次。

5.3.2 销毁互斥量

int pthread_mutex_destroy(pthread_mutex_t *mutex);

参数mutex是指要销毁的互斥量指针。

5.4 互斥量加锁与解锁

5.4.1 加锁

线程试图访问互斥锁的过程称之为加锁。

Pthreads中有两个试图访问互斥锁的函数,分别是pthread_mutex_lock()、pthread_mutex_trylock()

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);

pthread_mutex_lock()函数会一直阻塞直到互斥锁可用为止,pthread_mutex_trylock()则会尝试加锁,但通常会立即返回。

参数mutex是需要加锁的互斥量。函数成功返回0,失败返回个非0 的错误码。

其中在一个线程已持有锁的情况下,调用pthread_mutex_trylock()函数是错误码为EBUSY。

5.4.2 解锁

解锁是线程将互斥量有锁定状态变为解锁状态。

pthread_mutex_unlock(pthread_mutex_t *mutex);

参数mutex是需要解锁的互斥量,函数成功返回0,否则返回一个非0的错误码。

只有在线程进入了临界区之前正确的获取了适当的互斥量,才能在线程离开临界区时释放互斥量。

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
pthread_mutex_t lock;
void *printftest(void *argv){
        int* id = (int*)argv;
        //加锁
        pthread_mutex_lock(&lock);
        for(int i = 0;i < 5;i++){
                printf("pthread %d starts!\n",*id);
                sleep(1);
        }
        //解锁
        pthread_mutex_unlock(&lock);
}

int main(){
        printf("test demo is running...\n");
        pthread_t thread1,thread2;
        //初始化互斥量
        int lock_ret = pthread_mutex_init(&lock,NULL);
        if(lock_ret != 0)
                printf("lock init error!\n");
        //create
        int i = 1;
        int ret1 = pthread_create(&thread1,NULL,&printftest,(void*)&i);
        if(ret1 != 0)
                printf("thread 1 create error!\n");
        int j = 2;
        int ret2 = pthread_create(&thread2,NULL,&printftest,(void*)&j);
        if(ret2 !=0)
                printf("thread 2 create error!\n");
        pthread_join(thread1,NULL);
        pthread_join(thread2,NULL);
        printf("main exit!\n");
}

输出:

test demo is running...
pthread 2 starts!
pthread 2 starts!
pthread 2 starts!
pthread 2 starts!
pthread 2 starts!
pthread 1 starts!
pthread 1 starts!
pthread 1 starts!
pthread 1 starts!
pthread 1 starts!
main exit!

不加锁的状态...

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
pthread_mutex_t lock;
void *printftest(void *argv){
        int* id = (int*)argv;
        //加锁
//      pthread_mutex_lock(&lock);
        for(int i = 0;i < 5;i++){
                printf("pthread %d starts!\n",*id);
                sleep(1);
        }
        //解锁
//      pthread_mutex_unlock(&lock);
}

int main(){
        printf("test demo is running...\n");
        pthread_t thread1,thread2;
        //初始化互斥量
        int lock_ret = pthread_mutex_init(&lock,NULL);
        if(lock_ret != 0)
                printf("lock init error!\n");
        //create
        int i = 1;
        int ret1 = pthread_create(&thread1,NULL,&printftest,(void*)&i);
        if(ret1 != 0)
                printf("thread 1 create error!\n");
        int j = 2;
        int ret2 = pthread_create(&thread2,NULL,&printftest,(void*)&j);
        if(ret2 !=0)
                printf("thread 2 create error!\n");
        pthread_join(thread1,NULL);
        pthread_join(thread2,NULL);
        printf("main exit!\n");
}

输出:

test demo is running...
pthread 2 starts!
pthread 1 starts!
pthread 2 starts!
pthread 1 starts!
pthread 2 starts!
pthread 1 starts!
pthread 2 starts!
pthread 1 starts!
pthread 2 starts!
pthread 1 starts!
main exit!

可以看出,不加锁是乱序输出。

5.5 死锁和死锁的避免

5.5.1 死锁

死锁是指两个或者个以上的执行程序在执行过程中因争夺资源而造成的一种互相等待的现象。

例如,一个进行T1已锁定了一个资源R1,又想去锁定资源R2,而此时另一个线程T2已经锁定了资源R2,却又想去锁定资源R1,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待而无法执行。

根据我自己的理解,应该是:互斥锁就是1班教室门上的锁,当你想进教室偷偷干点事,就得上锁不然别人访问教室里面的资源,但是你临幸完以后,走的时候就得解锁,不能吃着碗里的还惦记着锅里的。现在1班的门上你进去以后上了两把锁,走的时候显然两把都要打开,不然别人还是进不去。其中这两把锁中有一把是2班的,你把锁给锁住了,别人就以为二班门锁上了,无法访问(死锁),最后就都排在了门口(阻塞)。

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
pthread_mutex_t lock1,lock2;
void *printftest1(void *argv){
        int* id = (int*)argv;
        //加锁
        pthread_mutex_lock(&lock1);
        for(int i = 0;i < 5;i++){
                printf("pthread %d starts!\n",*id);
                sleep(1);
        }
        //解锁
        pthread_mutex_unlock(&lock1);
}

void *printftest2(void *argv){
        int* id = (int*)argv;
        printf("pthread %d starts!\n",*id);

        //加锁
        pthread_mutex_lock(&lock2);
        pthread_mutex_lock(&lock1);
        for(int i = 0;i < 5;i++){
                printf("pthread %d starts!\n",*id);
                sleep(1);
        }
        pthread_mutex_unlock(&lock1);
        //解锁
//      pthread_mutex_unlock(&lock2);
        for(int i = 0;i < 5;i++)
                printf("pthread %d starts!\n",*id);

}
int main(){
        printf("test demo is running...\n");
        pthread_t thread1,thread2,thread3;
        //初始化互斥量
        int lock_ret1 = pthread_mutex_init(&lock1,NULL);
        if(lock_ret1 != 0)
                printf("lock1 init error!\n");
        int lock_ret2 = pthread_mutex_init(&lock2,NULL);
        if(lock_ret2 != 0)
                printf("lock2 init error!\n");
        //create
        int i = 1;
        int ret1 = pthread_create(&thread1,NULL,&printftest1,(void*)&i);
        if(ret1 != 0)
                printf("thread 1 create error!\n");
        int j = 2;
        int ret2 = pthread_create(&thread2,NULL,&printftest2,(void*)&j);
        if(ret2 !=0)
                printf("thread 2 create error!\n");
        int k = 3;
        int ret3 = pthread_create(&thread3,NULL,&printftest2,(void*)&k);
        if(ret3 !=0)
                printf("thread 3 create error!\n");
        pthread_join(thread1,NULL);
        pthread_join(thread2,NULL);
        pthread_join(thread3,NULL);
        printf("main exit!\n");
}

输出:

分析:进程2上锁之前的代码可以访问,之后的不行。进程2刚开始把1的锁也给锁住了,所以进程1不能执行,2后来把1的锁还回去了,1进行执行。但是2走的时候没有把lock2打开,导致自己班的同学不能继续访问进程2.

5.5.2 死锁的避免

当多个线程都需要相同的锁,但是按照不同的顺序加锁,死锁就很容易发生,如果能确保所有线程都是按照相同的顺序获得锁,那么死锁就不会发生。

例如,规定程序内有三个互斥锁的加锁顺序为mutexA -> mutexB -> mutexC,则线程t1、t2、t3线程操作顺序为:

t1t2

t3

lock(mutexA)lock(mutexA)lock(mutexB)
lock(mutexB)lock(mutexC)lock(mutexC)
lock(mutexC)  

6、条件变量

6.1 为什么需要条件变量

 

6.2 创建和销毁

6.2.1 创建条件变量

Pthreads使用pthread_cond_t类型的变量来表示条件变量。程序必须再使用pthread_cond_t变量之前对其进行初始化。

(1)静态初始化

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

(2)动态初始化

int pthread_cond_init(pthread_cond_t *restruct cond, const pthread_condattr_t *restrict attr);

参数cond是指向一个需要初始化的pthread_cond_t变量的指针,参数attr传递NULL值时,pthread_cond_init()将cond初始化为默认属性的条件变量。

函数成功返回0,否则返回一个非零的错误码。

静态初始化程序通常比动态初始化更有效。

6.2.2 销毁条件变量

int pthread_cond_destroy(pthread_cond_t *cond);

函数成功返回0,否则返回一个非零的错误码

6.3 等待和通知

6.3.1 等待

条件变量和条件测试是一起的,通常线程会对一个条件进行测试,如果条件不满足就会调用条件等待函数来等待条件满足。

条件等待函数有:pthread_cond_wait()、pthread_cond_timedwait(),函数原型如下:

int pthread_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrcit mutex);

int pthread_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t * restrcit mutex, const struct timespec *restrcit abstime);

pthread_cond_wait()在条件不满足时候一直等待

pthread_cond_timedwait()在条件不满足时候等待一段时间

  • 参数cond是指向一个条件变量的指针;
  • 参数mutex是一个指向互斥量的还真,线程在调用前应该拥有这个互斥量,当线程要加入条件变量的等待队列时,等待操作会使线程释放这个互斥量;
  • pthread_cond_timedwait()函数的第三个参数abstime是一个指向返回时间的指针,如果条件变量通知信号在没有此等待时间之前出现,则等待时间超时退出,abstime是绝对时间,而不是时间间隔。

以上函数执行公共返回0,否则返回一个非零的错误码。如果abstime指定的时间到期,其中pthread_cond_timedwait()错误码为:ETIMEOUT。

6.3.2 通知

当一个线程修改了某个参数可能使得条件变量所关联的条件变为true,他应该通知一个或多个等待在条件变量队列中的线程。

条件通知函数有:pthread_cond_siganl()、pthread_cond_broadcast()。其中pthread_cond_siganl()可以唤醒一个在条件变量等待队列中的线程,pthread_cond_broadcast()可以唤醒所有在条件变量等待队列中的线程。其函数原型如下:

int pthread_signal(pthread_cond_t *cond);

int pthread_broadcast(pthread_cond)t *cond);

参数cond是一个指向条件变量的指针。

函数执行成功返回0,否则返回一个非零的错误码。

这个类似于事件触发机制。


 

Linux C++多线程编程 1.介绍   使用多线程的理由之一是和进程相比,它是一种非常”节俭”的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种”昂贵”的多任务工作方式。而运行一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程,而且,线程间彼此切换所需的时间也... 阅读详情

相关推荐

Linux——多线程编程

•。

weixin_51526796的博客 1044

线程简介

FreeBSD下的线程     由于Unix系统本身并不存在线程这个概念,那么在Unix下引入线程就有两种比较直接的方式。     第一种方式是不在内核中实现线程,而在用户程序本身中实现线程,这实际是对线程的一种模拟,线程之间的切换和调度是在用户的进程内部进行的,这种方式就被称为用户空间的线程。这种线程的好处是实现非常简单,而且性能也非常好,因为线程之间的切换都在用户进程内部进行,切换开销比

Remy的学习记录 758

海康工业相机排雷笔记02-像素格式解析

工业相机使用过程记录 02-像素格式解析 通过将相机输出图像裸数据raw格式直接存图查看 一、黑白/Mono 直接使用相机测试图像存图: 图像中各部分灰度分别为:0、36、73、109、146、182、219、255,以灰度73列为例 (1)Mono 8 该列图像数据为十六进制49,十进制为73,那么Mono 8就是每个像素点占8bit,依次排列。 (2)Mono 10 该列图像数据为十六进制2401,由于相机均为小端数据,实际数据应解析为0124,十进制为292=73*4,那么Mono 10格式为每

yonghuming_66的博客 8355

Linux - pthread

pthread(POSIX thread)库是个标准C/C++多线程API库,线程没有父子之分。与标准 fork() 相比,线程带来的开销很小。内核无需单独复制进程的内存空间或文件描述符等等。这就节省了大量的 CPU 时间,使得线程创建比新进程创建快上十到一百倍。因为这一点,可以大量使用线程而无需太过于担心带来的 CPU 或内存不足。 1. 多线程基础 线程的操作包括:创建、终止、同步、调度、...

liqingbing12的专栏 1241

Fiddler Everywhere保姆级教程:10分钟搞定手机HTTPS抓包(附证书配置避坑指南)

本文提供Fiddler Everywhere移动端HTTPS抓包的保姆级教程,涵盖环境配置、代理设置、证书安装及常见问题排查。重点解析如何正确安装并信任Fiddler根证书以解密HTTPS流量,帮助开发者快速搭建抓包环境,高效分析应用网络行为,提升调试效率。

git9versioner的博客 703

2025年VSCode AI插件实战:如何用Devika+Continue+Tabby三件套10倍提升开发效率

本文详细介绍了2025年VSCode AI插件Devika、Continue和Tabby的三件套组合,如何通过智能开发工作流10倍提升开发效率。从项目架构生成到上下文感知辅助,再到本地化智能编码,这套方案实现了从需求分析到代码生成的全流程自动化,特别适合中大型项目开发。

weixin_26911099的博客 269

编译程序加不加 -lpthread 的区别

最近在优快云上看到一个帖子在讨论进程间共享的Posix mutex的锁定状态能否被子进程继承?,其中4楼的帖子给出了一个测试局部mutex能否被继承的例子: #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(void) { pid_t pid; pthread_mutex_t mut; pthrea.

qq295109601的博客 1168

数据标注工具:AiLabel.js实现标注 超详细教程 案例分享

背景:在前端开发过程中难免遇到针对图片的缩放平移;以及在图片上进行矢量数据、文本、标注的展示;如果你有上面的任何需求,恭喜你,找到组织了.... 在此背景下,AILabel.js出生了。 前面文章我们已经介绍过AiLabel.js了; https://blog.youkuaiyun.com/u012967771/article/details/113148527 npm下载地址:https://www.npmjs.com/package/ailabel github地址:https://github.com/d.

前端互助会 9365

多线程(pthread库)

多线程(pthread库实现)

m0_72873854的博客 3397

ethercat linux lan9252 - lan9253调试

linux 的ethercat测试

linsheng_111的专栏 1931

Dify 第 7 篇 Dify 应用介绍 + 聊天助手&Agent 应用关键点说明

本文介绍了Dify应用的核心概念与使用方法。Dify是基于大模型构建的实际场景应用,提供API、WebApp和易用界面。包含5种应用类型:聊天助手、文本生成、Agent、对话流和工作流。创建方式有模板创建、空白应用和DSL文件三种。重点讲解了聊天助手和Agent应用的关键功能:聊天助手支持提示词优化、知识库引用、文件处理等;Agent应用则强调推理能力、工具调用和任务自动化。文章还详细介绍了应用的调试、发布和管理工具,为开发者提供了全面的应用构建指南。

甘蓝的专栏 1882

Linux线程 | 创建 终止 回收 分离

线程是参与系统调度的最小单位。它被包含在进程之中,是进程中的实际运行单位。一个进程中可以创建多个线程,多个线程实现并发运行,每个线程执行不同的任务。每个线程都有其对应的标识,称为线程 ID,线程 ID 使用pthread_t数据类型来表示。

Happy_HZW的博客 563

数据库系统工程师软考备战:第三篇 - SQL语言深入解析与应用

​​创建表​​​修改表​​​删除表​。

Programming Talk 121

【Linux系统】Linux多线程详解

由上图可以很好地理解线程为什么是CPU调度的基本单位,在CPU看来,它只关心一个独立的执行流,无论进程内部是一个还是多个执行流,CPU都是以task_struct为单位来调度的。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。在操作系统的的视角下,Linux下没有真正意义的线程,而是用进程模拟的线程(LWP,轻量级进程),所以Linux不会提供直接创建线程的系统调用,最多提供创建轻量级进程的接口。个字节,也就是1MB。

fufufu_的博客 6771

《QGIS快速入门与应用基础》079:KML/KMZ格式:Google Earth兼容

KML/KMZ是专为地理可视化设计的空间数据格式,原生兼容Google Earth,支持点线面要素标注与多媒体集成。KML为XML文本格式,KMZ是其压缩版本。该格式优势在于可视化表现力强、跨平台共享便捷,适合地理标注、路线分享等场景,但存在属性支持弱、大数据量处理效率低等局限。QGIS中可便捷加载和导出KML/KMZ数据,需注意编码设为UTF-8、勾选"导出样式"以保留可视化效果。建议中小规模标注数据使用KMZ格式,确保关联资源完整,提升共享效率。

LuckyHanMo的博客 764

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Linux下多线程

Linux下多线程

zgwnb666的博客 1085

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Linux多线程详解

在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”。 - 每个进程至少都有一个执行线程。 线程在进程内部运行,本质是在进程地址空间内运行。在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更轻量化。透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流

m0_72532428的博客 1143

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Linux 多线程

一、线程理论基础 1、定义 线程( thread )技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者.传统的Unix也支持线程的概念,但是在一个进程( process )中只允许有一个线程,这样多线程就意味着多进程.现在,多线程技术已经被许多操作系统所支持,包括Windows/NT、Linux( thread )。 2、优点 (1)(与进程相比) ①和进程相比,它是一种非常“节俭”的多任务操作方式。在Linux系统下,启动一个新的进程必须分

weixin_42849105的博客 2423

【Linux】Linux下多线程

Linux下线程;线程的概念;进程地址空间和页表的理解;Linux线程控制,线程等待;封装C++版本线程库

weixin_63249832的博客 1106

linux C++ 多线程浅尝

学习了几天Linux多线程,大多是看的别人的博客,并试着写了个小例子,现在也把学到的一些东西记录下来,供以后查阅,属于初级入门的范围,望共勉。   进程是系统中程序执行和分配资源的基本单位,每个进程都有自己独立的数据段,代码段和堆栈段。而线程是系统中独立运行的最小单位,可以说操作系统分配CPU时间的最小单位,可以叫轻型的进程。一个进程可以有多个线程,在多进程情况下,每个进程都有着自己的地址空间,

月子G 的专栏 1万+

Linux——多线程

Linux——线程精析

2302_81486993的博客 1445

linux内核多线程,基于Linux内核的多线程技术的研究

摘 要 多线程技术是一项新的微处理器体系结构技术,它在传统并行指令集的基础上,添加了并行线程集,对操作系统来说,一个具有多线程的微处理器就相当于两个逻辑处理器。任何微处理器的使用都需要有操作系统的支持,Linux是UNIX类的操作系统,其发展十分迅猛,已经成为使用最为广泛的网络服务器操作系统。Linux提供的稳定性和强大网络支持能力必将使多线程技术发挥得更为快捷和顺畅。本文介绍了多线程技术的概念,...

weixin_39954464的博客 373

【Linux】Linux多线程

1.线程概念 什么是进程? 在一个程序里的一个执行路线叫做线程。更准确的定义:线程是一个进程内部的控制序列 一个进程至少都有一个执行线程 线程在进程内部运行,本质是在进程地址空间内运行 在linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程合理分配给每个执行流,就形成了线程执行流   线程的优点 创建一个新线程的代价要比一个新进程小的多 与进程之间的切换相比,线程之间的切换需要操作系统的工作少的多 线程占用的资

金金金金金的博客 246
上一篇: Linux进程间通信
下一篇: Linux网络编程
Utotao
博客等级 码龄9年 210粉丝 117原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值