Linux与C++11多线程编程(学习笔记)



多线程编程与资源同步

在Windows下,主线程退出后,子线程也会被关闭;

在Linux下,主线程退出后,系统不会关闭子线程,这样就产生了僵尸进程

3.2.1创建线程

  • Linux 线程的创建

    #include <unistd.h>
    #include <stdio.h>
    #include <pthread.h>
    void* threadfunc(void* arg)
    {while (1){sleep(1);printf("I am a new thread!!!\n");}return NULL;
    }int main()
    {pthread_t threadid;pthread_create(&threadid, NULL,threadfunc, NULL);while (1){}return 0;
    }
    
  • Windows CRT1提供的线程创建函数

    #include <process.h>
    #include <stdio.h>
    unsigned int __stdcall threadfun(void* args)
    {while (true){printf("I am new thread!");}return 0;
    }
    int main(int argc,char* argv[])
    {unsigned int threadid;_beginthreadex(0, 0, threadfun, 0, 0, &threadid);while (true)//不让主线程退出{}return 0;
    }
    
  • C++ 提供的std::thread

    #include <iostream>
    #include <thread>void threadproc1()
    {while (true){printf("I am aNew Thread!!");}
    }void threadproc2(int a,int b)
    {while (true){printf("I an Thread2");}
    }int main()
    {std::thread t1(threadproc1);std::thread t2(threadproc2, 1, 2);while (true){}
    }
    

    这种方法容易出错,原因如下:

    #include <iostream>
    #include <thread>void threadproc1()
    {while (true){printf("I am aNew Thread!!");}
    }void func()
    {std::thread t(threadproc1);
    }
    int main()
    {func();while (true){}
    }
    

    这段代码实际上试运行不了的,在vs2019 release模式下结果:

    image-20210703231616070

    在func函数调用完成后,func中的局部变量t被销毁,而此时线程函数仍然在运行.所以使用std::thread创建线程必须保证线程函数运行期间,线程对象始终有效!!

    当然,我们也可以使用detach方法解决这个问题,使线程函数和线程对象脱离

    ...
    void func()
    {std::thread t(threadproc1);t.detach();
    }
    ...
    

    这样就可以运行了,但是不推进这样做,因为我们需要线程对象对线程进行管理

    3.2.2线程ID

    下面介绍一下Linux系统线程ID本质

  • Linux系统线程ID本质

    在Linux系统中有三种方法可以获取一个线程的ID

    • 调用pthread_create函数时,可以通过第一个参数获取线程ID
    • 在需要获取ID的线程中调用pthread_self函数获取
    • 通过系统调用获取线程ID

    其中,方法一和方法二获取线程ID的结果都是一样的,都是pthread_t类型

    image-20210703234233186

不同的进程可能有同样的地址内存块(共享内存),所以通过方法一和方法二获取到的线程ID可能不是全系统唯一的,而方法三获取的线程ID是全系统唯一的,就是LWP(轻量级进程)2

  • C++ 获取线程ID的方法

    #include <thread>
    #include <iostream>
    #include <sstream>
    void worker_thread_func()
    {while (true){}
    }int main()
    {//获取线程t的idstd::thread t(worker_thread_func);std::thread::id worker_thread_id=t.get_id();std::cout<<worker_thread_id<<std::endl;//获取主线程的idstd::thread::id main_thread_id = std::this_thread::get_id();std::cout<<main_thread_id<<std::endl;while (true){}
    }
    

    运行结果(ubuntu20):

    image-20210704182253896

3.2.3 等待线程结束

  • 在Linux下等待线程结束

    Linux 线程库提供了pthread_join函数,用来等待某线程的退出并接收他的返回值

    这种操作被称为汇接(join)

    Declared in: pthread.h  
    static int pthread_join(pthread_t __th, void * *__thread_return)
    

    参数__th是需要等待的线程ID;参数__thread__return是输出参数,用于接受被等待线程的退出码,

    可以再调用pthread_exit()时指定退出码

    Declared in: pthread.h  
    static void pthread_exit(void *__retval)
    

    其中__retval可以通过__thread_return参数获得

    下面来展示一个实例,在程序启动时开启一个工作线程,工作线程将当前系统时间写入一个文件后,主线程等待工作线程退出后,从文件中读取时间,并将其输出

    #include <cstdio>
    #include <string>
    #include <pthread.h>
    #include <cstring>
    #define TIME_FILENAME "time.txt"
    void* fileThreadFunc(void* arg)
    {time_t now = time(nullptr);tm* t = localtime(&now);char timeStr[32]={0};snprintf(timeStr,32,"%04d/%02d %02d:%02d:%02d",t->tm_year+1900,t->tm_mon+1,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec);FILE* fp = fopen(TIME_FILENAME,"w");if(fp == nullptr){printf("打开文件(time.txt)失败\n");return nullptr;}size_t sizeToWrite=strlen(timeStr) +1;size_t ret = fwrite(timeStr,1,sizeToWrite,fp);if(ret != sizeToWrite){printf("写入错误!\n");}fclose(fp);return nullptr;
    }int main()
    {pthread_t fileThreadID;int ret = pthread_create(&fileThreadID, nullptr,fileThreadFunc, nullptr);if(ret != 0){printf("创建线程失败\n");return -1;}int* retval;pthread_join(fileThreadID,(void**)&retval);FILE* fp = fopen(TIME_FILENAME,"r");if(fp == nullptr){printf("打开文件失败!\n");return -2;}char  buf[32] = {0};int sizeRead = fread(buf,1,32,fp);if(sizeRead == 0){printf("读取文件失败");fclose(fp);return -3;}printf("Current time is: %s.\n",buf);fclose(fp);return 0;}
    

    执行结果如下:

    image-20210704185826945

  • Windows 等待线程结束

    在Windows下我们可以使用WaitForSingleObject function (synchapi.h)WaitForMultipleObjects function (synchapi.h)

  • C++11提供的等待线程结束的函数

    #include <cstdio>
    #include <cstring>
    #include <thread>
    #define TIME_FILENAME "time.txt"
    void fileThreadFunc()
    {time_t now = time(nullptr);tm* t = localtime(&now);char timeStr[32]={0};snprintf(timeStr,32,"%04d/%02d %02d:%02d:%02d",t->tm_year+1900,t->tm_mon+1,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec);FILE* fp = fopen(TIME_FILENAME,"w");if(fp == nullptr){printf("打开文件(time.txt)失败\n");return ;}size_t sizeToWrite=strlen(timeStr) +1;size_t ret = fwrite(timeStr,1,sizeToWrite,fp);if(ret != sizeToWrite){printf("写入错误!\n");}fclose(fp);return;
    }int main()
    {std::thread t(fileThreadFunc);if(t.joinable()) t.join();FILE* fp = fopen(TIME_FILENAME,"r");if(fp == nullptr){printf("打开文件失败!\n");return -2;}char  buf[32] = {0};int sizeRead = fread(buf,1,32,fp);if(sizeRead == 0){printf("读取文件失败");fclose(fp);return -3;}printf("Current time is: %s.\n",buf);fclose(fp);return 0;
    }
    

3.4.3 C++11 对整型变量原子操作的支持

C++11 提供了对整形变量原子操作的支持

std::atomic这是一个模板类型

Defined in header <atomic>
template< class T >
struct atomic;

例子

#include <atomic>
#include <iostream>
int main()
{std::atomic<int> value{1};value++;//自增,原子操作std::cout<<value<<std::endl;
}

C++ 线程同步对象

C++ 新标准中新增用于线程同步的std::mutexstd::condition_variable

3.7.1 std::mutex系列

这个系列类型的对象均提供了加锁lock,尝试加锁trylock和解锁unlock的方法

#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
int  g_num =0 ;
std::mutex g_num_mutex;
void slow_increment(int id)
{for (int i = 0; i < 10; ++i){g_num_mutex.lock();++g_num;std::cout<<id<<"->"<<g_num<<std::endl;g_num_mutex.unlock();}std::this_thread::sleep_for(std::chrono::seconds(1));
}
int main()
{std::thread t1(slow_increment,0);std::thread t2(slow_increment,1);t1.join();t2.join();return 0;
}

输出:

image-20210704214043716

3.7.2 std::shared_mutex

std::shared_mutex的底层实现是操作系统提供的读写锁,也就是说,在有多个线程对共享资源读且少许线程对共享资源写的情况下,std::shared_mutexstd::mutex效率更高

shared_mutex 通常用于多个读线程能同时访问同一资源而不导致数据竞争,但只有一个写线程能访问的情形。

3.7.3 std::condition_variable

Member functions

(constructor)constructs the object (public member function)
(destructor)destructs the object (public member function)
operator=[deleted]not copy-assignable (public member function)
Notification
notify_onenotifies one waiting thread (public member function)
notify_allnotifies all waiting threads (public member function)
Waiting
waitblocks the current thread until the condition variable is woken up (public member function)
wait_forblocks the current thread until the condition variable is woken up or after the specified timeout duration (public member function)
wait_untilblocks the current thread until the condition variable is woken up or until specified time point has been reached (public member function)
Native handle
native_handlereturns the native handle (public member function)

`

TCP网络编程的基本流程

Linux与C++11多线程编程(学习笔记)

Linux select函数用法和原理

socket的阻塞模式和非阻塞模式(send和recv函数在阻塞和非阻塞模式下的表现)

connect函数在阻塞和非阻塞模式下的行为

获取socket对应的接收缓冲区中的可读数据量


  1. CRT原先是指Microsoft开发的C Runtime Library(C语言运行时库),用于操作系统的开发及运行。后来在此基础上开发了C++ Runtime Library,所以现在CRT是指Microsoft开发的C/C++ Runtime Library。在VC的CRT/SRC目录下,可以看到CRT的源码,不仅有C的,也有C++的。CRT(Microsoft’s C/C++ Runtime Library)的一个真子集(主要是C++ Runtime Library)是一个符合(或至少是企图符合)C++标准的C++库。而Windows API(以及Windows的其他许多部分)都是在CRT的基础上开发的。 ↩︎

  2. 既然称作轻量级进程,可见其本质仍然是进程,与普通进程相比,LWP与其它进程共享所有(或大部分)逻辑地址空间和系统资源,一个进程可以创建多个LWP,这样它们共享大部分资源;LWP有它自己的进程标识符,并和其他进程有着父子关系;这是和类Unix操作系统的系统调用vfork()生成的进程一样的。LWP由内核管理并像普通进程一样被调度。Linux内核是支持LWP的典型例子。Linux内核在 2.0.x版本就已经实现了轻量进程,应用程序可以通过一个统一的clone()系统调用接口,*用不同的参数指定创建轻量进程还是普通进程,通过参数决定子进程和父进程共享的资源种类和数量,这样就有了轻重之分*。在内核中, clone()调用经过参数传递和解释后会调用do_fork(),这个核内函数同时也是fork()、vfork()系统调用的最终实现。*在大多数系统中,LWP与普通进程的区别也在于它只有一个最小的执行上下文和调度程序所需的统计信息,而这也是它之所以被称为轻量级的原因。* 因为LWP之间共享它们的大部分资源,所以它在某些应用程序就不适用了;这个时候就要使用多个普通的进程了。例如,为了避免内存泄漏(a process can be replaced by another one)和实现特权分隔(processes can run under other credentials and have other permissions)。img ↩︎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/408171.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TCP网络编程的基本流程

TCP网络编程的基本流程 对于服务端,通常为以下流程: 调用socket函数创建socket调用bind函数将socket绑定到某个IP和端口上调用listen开始监听当有客户端请求连接上来时,调用accept函数接受连接,产生一个新的socket基于新产生的socket调用send或recv函数,开始与客户端进行数据…

Linux select函数用法和原理

select函数的用法和原理 Linux上的select函数 select函数用于检测一组socket中是否有事件就绪.这里的事件为以下三类: 读事件就绪 在socket内核中,接收缓冲区中的字节数大于或者等于低水位标记SO_RCVLOWAT,此时调用rec或read函数可以无阻塞的读取该文件描述符,并且返回值大于…

常见日期方法荟萃

一.如何获得当月有多少天 intmSystem.DateTime.DaysInMonth(System.DateTime.Now.Year,System.DateTime.Now.Month);二.日期型格式处理通用方法1.在webconfig中配置如下<add key"ShortDatePattern"value"MM-dd-yyyy"/><add key"LongDatePatt…

socket的阻塞模式和非阻塞模式(send和recv函数在阻塞和非阻塞模式下的表现)

socket的阻塞模式和非阻塞模式 无论是Windows还是Linux,默认创建socket都是阻塞模式的 在Linux中,可以再创建socket是直接将它设置为非阻塞模式 int socket (int __domain, int __type, int __protocol)将__type增加SOCK_NOBLOCK 不仅如此,在Linux上直接利用accept函数返回…

connect函数在阻塞和非阻塞模式下的行为

connect函数在阻塞和非阻塞模式下的行为 当socket使用阻塞模式时,connect函数会阻塞到有明确结果才会返回,如果网络环境较差,可能要等一会,影响体验, 为了解决这个问题,我们使用异步connect技术 创建socket,将socket设置为非阻塞模式 调用connect函数,此时无论connect函数是…

获取socket对应的接收缓冲区中的可读数据量

获取socket对应的接收缓冲区中的可读数据量 本文介绍如何获取当前socket对应的接收缓冲区的可读数据量 在Linux上可以使用ioctl函数 #include <sys/ioctl.h>int ioctl (int __fd, unsigned long int __request, ...)来看一个例子: #include <sys/types.h> #in…

Linux epoll的用法

Linux epoll的用法 epollfd_create函数 #include <sys/epoll.h>int epoll_create (int __size)参数含义__size此参数从Linux 2.6.8后就不再使用了,但必须设置成大于零的值 返回值含义>0可用的epollfd-1调用失败 epollfd_ctl函数 有了epollfd,我们需要将要检测事件…

windows网络编程

windows网络编程 TCP编程 服务端 这里我们有几点需要注意: 使用WSAStartup初始化网络库,即将与socket函数相关dll文件加载到进程地址空间中退出时,使用WSACleanup()卸载相关dll文件与Linux使用close函数关闭socket不同,windows需要使用closesocket函数关闭socket WSAStart…

TCP服务器epoll的多种实现

TCP服务器epoll的多种实现 对于网络IO会涉及到两个系统对象 用户空间中进程或者线程操作系统内核 比如发生read操作时就会经历两个阶段 等待数据就绪将数据从内核缓冲区拷贝到用户缓冲区 由于各个阶段多有不同的情况,一组合么就产生了多种网络 IO 模型 阻塞IO 在Linux中…