C++四线程编制程序轻易实例

      
linux上互斥量的函数是:

}

信号量(Semaphore)【重点】

信号量可以分为:

  1. 二进制信号量(binary
    semaphore):只允许信号量取0或1值,其同时只能被一个线程获取。
  2. 整型信号量(integer
    semaphore):信号量取值是整数,它可以被多个线程同时获得,直到信号量的值变为0。
  3. 记录型信号量(record
    semaphore):每个信号量s除一个整数值value(计数)外,还有一个等待队列List,其中是阻塞在该信号量的各个线程的标识。当信号量被释放一个,值被加一后,系统自动从等待队列中唤醒一个等待中的线程,让其获得信号量,同时信号量再减一。

互斥量和信号量的区别

  1. 互斥量用于线程的互斥,信号线用于线程的同步。
    这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。
    互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
    同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源
  2. 互斥量值只能为0/1,信号量值可以为非负整数。
    也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。
  3. 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

         SIZE_T dwStackSize,

其实最开始我是搞windows下编程的,包括windows编程,windows
驱动,包括usb驱动,ndis驱动,pci驱动,1394驱动等等,同时也一条龙服务,做windows下的应用程序开发,后面慢慢的我又对linux开发产生比较深的兴趣和爱好,就转到搞linux开发了。在接下来的我还会写一些博客,主要是写linux编程和windows编程的区别吧,现在想写的是linux下usb驱动和windows下usb驱动开发的区别,这些都是后话,等我将linux多线程和windows多线程讲解完后,我再写一篇usb驱动,谈谈windows
和linux usb驱动的东东。好了,言归正传。开始将多线程了。

               ReleaseMutex(hMutex);

4、线程的销毁

Linux线程并不是在首次调用的线程main函数返回时自动销毁。

  • pthread_join函数不仅会等待线程终止,还会引导线程销毁。但线程终止前,调用该函数的线程将进入阻塞状态。
  • 调用pthread_detach

int pthread_detach(pthread_t thread);
成功时返回0,失败时返回其他值。
参数解释:
thread:终止的同时需要销毁的线程ID

调用上述函数不会引起线程终止或进入阻塞状态,可以通过该函数引导销毁线程创建的内存空间。
调用该函数后不能再针对相应线程调用pthread_join函数。

        
回顾一下自己学习过的知识,首先是Java中的多线程知识,创建多个线程,同时在多线程中使用同步机制。还有就是C++中的多线程机制,使用系统API接口调用,createThread或者是_beginthread函数调用操作系统接口实现多线程,同时根据返回的句柄,等待线程,实现线程的同步。之前学习C++的线程同步,了解的有两种方法,一种是CreateMutex,返回一个互斥锁HADNLE,使用WaitForSimpleObject,
ReleaseMutex。还有就是 CriticalSelection。

(2)多线程和多进程相比,一个明显的优点就是线程之间的通信了,对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。但是对于多线程就不一样了。www.linuxidc.com他们之间可以直接共享数据,比如最简单的方式就是共享全局变量。但是共享全部变量也要注意哦,呵呵,必须注意同步,不然后果你知道的。呵呵。

该函数用于创造一个独占资源,第一个参数我们没有使用,可以设为NULL,第二个参数指定该资源初始是否归属创建它的进程,第三个参数指定资源的名称。

基础知识

    return 0;

(1)共享全局变量,这种方法是最容易想到的,呵呵,那就首先讲讲吧,比如说吧,上面的问题,第一步要向第2步传递收据,我们可以之间共享全局变量,让两个线程之间传递数据,这时主要考虑的就是同步了,因为你后面的线程在对数据进行操作的时候,你第一个线程又改变了数据的内容,你不同步保护,后果很严重的。你也知道,这种情况就是读脏数据了。在这种情况下,我们最容易想到的同步方法就是设置一个bool
flag了,比如说在第2个线程还没有用完数据前,第一个线程不能写入。有时在2个线程所需的时间不相同的时候,怎样达到最大效率的同步,就比较麻烦了。咱们可以多开几个缓冲区进行操作。就像生产者消费者一样了。如果是2个线程一直在跑的,由于时间不一致,缓冲区迟早会溢出的。在这种情况下就要考虑了,是不让数据写入还是让数据覆盖掉老的数据,这时候就要具体问题具体分析了。就此打住,呵呵。就是用bool变量控制同步,linux
和windows是一样的。

eg3:

2、线程创建及运行

  • 线程创建

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

成功时返回0,失败时返回其他值。
参数解释:
1、thread:
保存新创建线程ID的变量地址值。线程与进程相同,也需要用于区分不同线程的ID。
2、attr:用于传递线程属性的参数,传递NULL时,创建默认属性的线程。
3、start_routine:相当于线程main函数的、在单独执行流中执行的函数地址值(函数指针)。
4、arg:通过第三个参数传递调用函数时包含传递参数信息的变量地址值。

实例:

#include <stdio.h>
#include <pthread.h>
#include <unistcl.h>
void * thread_main(void *arg);

int main(int argc,char * argv[])
{
    pthread_t t_id;//存放新线程id
    int thread_param = 5;

    if(pthread_create(&t_id,NULL,thread_main,(void*)&thread_param) != 0)
    {
        puts("pthread_create() error!");
        return -1;
    }
    sleep(10);
    fputs("end of main");
    return 0;
}
void * thread_main(void *arg)
{
    int i;
    int cnt = *((int *)arg);//传入的参数是(void *)&thread_param
    for(i = 0;i < cnt;i++)
    {
        sleept(1);
        puts("running main");
    }
    return NULL;
}
  • 控制线程的执行流

int pthread_join(pthread_t thread,void ** status);
成功时返回0,失败时返回其他值。
参数解释:
1、thread: 该参数值ID的线程终止后才会从该函数返回。
2、status: 保存线程的main函数返回值的指针变量地址值。

调用该函数的进程(或线程)将进入等待状态,直到第一个参数为ID线程终止为止。

 

HANDLE
WINAPI CreateMutex(
__in          LPSECURITY_ATTRIBUTES lpMutexAttributes,
__in          BOOL bInitialOwner,
__in          LPCTSTR lpName
);
可以可用来创建一个有名或无名的互斥量对象
第一参数
可以指向一个结构体SECURITY_ATTRIBUTES 一般可以设为null;
第二参数
指当时的函数是不是感应感应状态
FALSE为当前拥有者不会创建互斥
第三参数
指明是否是有名的互斥对象
如果是无名
用null就好。

}

线程和进程的差异
  • 每个进程的内存空间都由保存全局变量的“数据区”向malloc等函数的动态分配提供空间的堆(Heap)函数运行时使用的栈(Stack)构成。每个进程都拥有这种独立空间。
  • 通过只分离栈(函数运行时使用)区域,可以获得如下优势:
    1.上下文切换时不需要切换数据区和堆。
    2.可以利用数据区和堆交换数据。
    线程为了保持多条代码执行流而隔开了栈区域。
  • 多个线程将共享数据区和堆。为了保持这种结构,线程将在进程内创建并运行。
    进程:在操作系统构成单独执行流的单位。
    线程:在进程构成单独执行流的单位。

unitptr_t _beginthreadex(

);

    HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL);

LINUX

         该WINAPI参数介绍:

反正我觉得在这种情况下,采用多线程比较理想。比如说你要做一个任务分2个步骤,你为提高工作效率,你可以多线程技术,开辟2个线程,第一个线程就做第一步的工作,第2个线程就做第2步的工作。但是你这个时候要注意同步了。因为只有第一步做完才能做第2步的工作。这时,我们可以采用同步技术进行线程之间的通信。

       while(1) { 

2、线程创建

HANDLE CreateThread(
LPSECURITY_ATTRIBUTES IpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_STRART_ROUTINE IpStartAddress,
LPVOID IpParameter,
DWORD dwCreationFlags,
LPDWORD IpThread
);

成功时返回线程句柄(用于区分内核对象的整数型句柄),失败时返回NULL。
参数解释:
1.IpThreadAttributes 线程安全相关信息,使用默认设置时传递NULL
2.dwStartSie 要分配给线程的栈大小,传递0时生成默认大小的栈
3.IpStartAddress 传递线程的main函数信息
4.IpParameter 调用main函数时传递的参数信息
5.dwCreationFlags 用于指定线程创建后的行为,传递0时,线程创建后立即进入可执行状态
6.IpThreadId 用于保存线程ID的变量地址

 ####PS:句柄、内核对象和ID间的关系
> 线程也属于操作系统管理的资源,因此会伴随着内核对象的创建,并为了引用内核对象而返回**句柄**。
可以通过句柄区分内核对象,通过内核对象可以区分线程。线程句柄成为分区线程的工具。
**句柄的整数值在不同的进程中可能出现重复,但线程ID在跨进程范围内不会出现重复。**

 ####创建“使用线程安全标准C函数”的线程【一般使用此函数创建线程】
> uintptr_t _beginthreadex(
void * security,//NULL
unsigned stack_size,//0
unsigned (* start_address)(void *),
void * arglist,
unsigned initflag,
unsigned * thraddr
);




####3.内核对象的两种状态
- 资源类型不同,内核对象也含有不同的信息。应用程序实现过程中需要特别关注的信息被赋予某种“状态”(State)。
- 线程内核对象中需要重点关注线程是否已终止,所以终止状态又称“signaled状态”,未终止状态成为“non-signaled”状态。
- 进程或线程终止时,相应的内核对象会变为signaled状态。
> DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);
成功时返回事件信息,失败时返回WAIT_FAILED。(进入signaled状态返回WAIT_OBJECT_0,超时返回WAIT_TIMEOUT)
参数解释:
1.hHandle 查看状态的内核对象句柄
2.dwMilliseconds 以1/1000秒为单位指定超时,传递INFINITE时函数不会返回,知道内核对象变成signaled状态

>**该函数由于发生事件(变为signaled状态)返回时,有时会把相应内核对象再次改为non-signaled状态。这种可以再次进入non-signaled状态的内核对象成为“auto-reset”模式的内核对象,而不会自动跳转到non-signaled状态的内核对象成为“manual-reset模式”的内核对象。**

####4.用户模式(User mode)和内核模式(Kernal mode)
- Windows操作系统的运行方式(程序运行方式)是“双模式操作”(Dual-mode Operation),这意味着Windows在运行过程中存在如下两种模式
1、用户模式:运行应用程序的基本模式,禁止访问物理设备,而且会限制访问的内存区域
2、内核模式:操作系统运行时的模式,不仅不会限制访问的内存区域,而且访问的硬件设备也不会受限
内核是操作系统的核心模块,可以简单定义为如下形式
1、用户模式:应用程序的运行模式
2、内核模式:操作系统的运行模式
Windows操作系统不会一直停留在用户模式,而是在用户模式和内核模式之间切换。
- 用户模式->内核模式->用户模式
从用户模式切换到内核模式是为了创建资源,从内核模式再次切换到用户模式时为了执行应用程序的剩余部分。
#####基于CRITICAL_SECTION的同步(用户模式的同步方法)
#####内核模式的同步方法
######(1)、基于互斥量(Mutual Exclusion)对象的同步
> 创建:

HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES IpMutexAttributes,BOOL bInitialOwner,LPCTSTR
IpName);

成功时返回创建的互斥量对象句柄,失败时返回NULL
参数解释:
1、IpMutexAttributes 传递安全相关的配置信息,使用个默认安全设置时可以传递NULL
2、bInitialOwner 如果为TRUE,则创建出的互斥量对象属于调用该函数的线程,同时进入non-signaled状态;如果为false,则创建出的互斥量对象不属于任何线程,此时状态为signaled。
3、IpName 用于命名互斥量对象。传入NULL时创建无名的互斥量对象。
 销毁:
BOOL CloseHandle(HANDLE hObject);
成功时返回TRUE,失败时返回FALSE

- 获取和释放互斥量:
获取为WaitForSingleObject函数,释放为ReleaseMutex(HANDLE hMutex);
WaitForSingleObject(hMutex,INFINITE);
...
ReleaseMutex(hMutex);

######(2)、基于信号量对象的同步
>创建:
HANDLE CreateSemaphore(
LPSECURITY _ATTRIBUTES IpSemaphoereAttributes,LONG lInitialCount,
LONG lMaximumCount,LPCTSTR IpName);
成功时返回创建的信号量对象的句柄,失败时返回NULL。

WaitForSingleObject(hSemaphore,INFINITE);
//临界区开始
...
//临界区结束
ReleaseSemaphore(hSemaphore,1,NULL);

######(3)、基于事件对象的同步
- 事件同步对象方法创建对象时,可以在自动以non-signaled状态运行的auto-reset模式和与之相法的manual-reset模式中任选其一。
- 事件对象的主要特点是可以创建manual-reset模式的对象。
> HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES IpEventAttributes,BOOL bManualReset,
BOOL bInitialState,LPCTSTR IpName);
成功时返回创建的事件对象句柄,失败时返回NULL。
参数解释:
1、IpEventAttributes 安全配置相关参数,采用默认安全配置时传入NULL
2、bManualReset 传入TRUE时创建manual-reset模式的事件对象,传入FALSE时创建auto-reset模式的事件对象。
3、bInitialState 传入TRUE时创建signaled状态的事件对象,传入FALSE时创建non-signaled状态的事件对象。
4、IpName 用于命名事件对象。出入NULL时创建无名的事件对象。

BOOL ResetEvent(HANDLE hEvent);//to the non-signaled
BOOL SetEvent(HANDLE hEvent);//to the signaled

        DWORD fdwReason,

(1)因为多线程彼此之间采用相同的地址空间,共享大部分的数据,这样和多进程相比,代价比较节俭,因为多进程的话,启动新的进程必须分配给它独立的地址空间,这样需要数据表来维护代码段,数据段和堆栈段等等。

 

1、线程的概念

上下文切换 :
运行程序前需要将相应进程信息读入内存,如果运行进程A后需要紧接着运行程序B,就应该讲进程A相关信息移出内存,并读入进程B相关信息。

为了保持多进程的优点,同时在一定程度上克服其缺点,人们引入了线程(Thread).这是为了将进程的各种劣势降到最低限度而设计的一种“轻量级进程”。线程相比于进程的优点:
1.线程的创建和上下文切换比进程的创建和上下文切换更快。
2.线程间数据交换时无需特殊技术。

   
线程什么时候会终止呢?一下几种情况:线程的起始执行函数正常的返回;在线程起始执行函数结束之前,执行函数中发生异常,从而使线程崩溃;线程本身或者是其他线程调用WIN32函数中的ExitThread和TerminateThread函数;进程退出

 

C++本身并没有提供任何多线程机制,但是在windows下,我们可以调用SDK win32
api来编写多线程的程序,下面就此简单的讲一下:

1、内核对象(Kernel Objects)

  • 操作系统创建的资源(Resource)有很多种,如进程、线程、文件及信号量、互斥量等。它们都是由Windows操作系统创建并管理的资源。
  • 操作系统为了记录相关信息的方式管理各种资源,在其内部生成数据块(亦可视为结构体变量)。
  • 每种资源需要维护的信息不同,所以每种资源拥有的数据块格式也有差异。这类数据块称为”内核对象“。
  • 内核对象归操作系统所有
    内核对象的创建、管理、销毁时机的决定等工作均由操作系统完成。
    内核对象就是为了管理线程、文件等资源而由操作系统创建的数据块,其创建者和拥有者均为操作系统。

         LPSECURITY_ATTRIBUTES  lpThreadAttributes,

(3)在多cpu的情况下,不同的线程可以运行不同的cpu下,这样就完全并行了。

    BOOL bInitialOwner,                       // initial owner

基础知识

        return -1;

第一个是
创建的互斥对象的句柄。第二个是
表示将在多少时间之后返回
如果设为宏INFINITE
则不会返回
直到用户自己定义返回。

这条语句创造了一个名为screen并且归属于创建它的进程的资源

3、线程同步

任何内存空间,只要被同时访问,都可能发生为问题。P296 解释产生问题原因

 

很早以前就想写写linux下多线程编程和windows下的多线程编程了,但是每当写时又不知道从哪个地方写起,怎样把自己知道的东西都写出来,下面我就谈谈linux多线程及线程同步,并将它和windows的多线程进行比较,看看他们之间有什么相同点和不同的地方。

{

互斥量(Mutex)【重点】

互斥量表示不允许多个线程同时访问。主要用于解决线程同步访问的问题。在同一个线程中,为了防止死锁,系统不允许连续两次对Mutex加锁。

2.2线程的终止

既然讲道了这里,就再讲讲其它同步的方法。同样
针对上面的这个问题,共享全局变量同步问题。除了采用bool变量外,最容易想到的方法就是互斥量了。呵呵,也就是传说中的加锁了。windows下加锁和linux下加锁是类似的。采用互斥量进行同步,要想进入那段代码,就先必须获得互斥量。 

 

Windows

创建一个线程将返回一个句柄HANDLE,指向新创建的线程内核对象,我们在随后将这个值传递给其他的Win32系统函数已提取线程的信息、与线程对象交互,或者使用这个新创建的线程执行一些操作。(HANDLE句柄类型和指针类型的大小是一样的,表示进程句柄表中的索引,句柄表示的是内核对象,在托管代码中用IntPtr和SafeHandle来表示HANDLE)当线程不在需要获取线程的信息的时候,我们要关闭线程句柄,以避免线程对象无限期的保持活跃状态。

DWORD WINAPI WaitForSingleObject(

执行上述代码,这次我们可以清楚地看到在屏幕上交错地输出Fun display!和main
display!,我们发现这两个函数确实是并发运行的,细心的读者可能会发现我们的程序是每当Fun函数和main函数输出内容后就会输出换行,但是我们看到的确是有的时候程序输出换行了,有的时候确没有输出换行,甚至有的时候是输出两个换行。这是怎么回事?下面我们把程序改一下看看:

追梦的飞飞

 
__in          HANDLE
hHandle,

int main()

    unsigned stack_size,

 
__in          DWORD
dwMilliseconds

DWORD WaitForSingleObject(

    }

图片 1

  

 

首先我们讲讲为什么要采用多线程编程,其实并不是所有的程序都必须采用多线程,有些时候采用多线程,性能还没有单线程好。所以我们要搞清楚,什么时候采用多线程。采用多线程的好处如下:

    LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD

    void * arglist,

针对这种情况,我们首先讲讲多线程之间的通信,在windows平台下,多线程之间通信采用的方法主要有:

      while(1) {

        
但是我们还可以进一步的提高程序的并发程度。在一个进程中可以创建多个线程,每一个线程执行程序中的不同的部分,从而是程序可以得到并发。默认情况下,一个Windows进程只包含一个线程,但实际上可以在该线程的基础之上创建多个线程,是操作系统把这些线程放在不同的处理器上去执行。在C#中也是有多线程机制,因为CLR垃圾收集器使用了一个独立的终结器线程来回收资源。

      
windows下互斥量的函数有:createmutex
创建一个互斥量,然后就是获得互斥量waitforsingleobject函数,用完了就释放互斥量ReleaseMutex(hMutex),当减到0的时候
内核会才会释放其对象。下面是windows下与互斥的几个函数原型。

{

    void * security,

那么为什么我们把eg2改成eg3就可以正确的运行呢?原因在于,多个线程虽然是并发运行的,但是有一些操作是必须一气呵成的,不允许打断的,所以我们看到eg2和eg3的运行结果是不一样的。

 

表示暂停1秒

 

  

DWORD WINAPI MyThreadStart(LPVOID);

该函数用于释放一个独占资源,进程一旦释放该资源,该资源就不再属于它了,如果还要用到,需要重新申请得到该资源。申请资源的函数如下

        
线程并发,在编程中十分重要的一个领域,C++多线程编程,其实让我们更加深入的学习C/C++编程的知识,其实在我们自己学习的C/C++都是太浅了,比如编译的时候的标准我们都不知道,所以C/C++是一块非常大的领域,千万别轻易的说自己熟练掌握C/C++编程了。至少我不敢这么去说。

}

         LPDWORD, lpThreadId);

      CloseHandle(hThread);

        
Windows多个线程之间可以访问进程中共享的资源,包括一个虚拟内存地址空间,线程可以通过对相同的地址内存进行读取和写入等操作来实现数据共享和互相通信,还有其他的共享资源比如windows进程相关信息,比如句柄以及安全令牌。

我们可以看到主线程(main函数)和我们自己的线程(Fun函数)是随机地交替执行的,但是两个线程输出太快,使我们很难看清楚,我们可以使用函数

                  DWORD WINAPI ThreadProc(LPVOID
lpParameter);
这个函数的返回值将会作为线程的退出状态码。

      CloseHandle(hThread);

        
今天有什么事情呢?首先是捷游的二面通知,O(∩_∩)O~,我比我宿舍的那三个都要早,关键是呢,我二面的时候竟然记错了时间,提前了一天去面试,让HR姐姐找了半天我的资料都没找到,就以为我是霸面的,管我要短信,一看,是11.1日,我以为是10.31,好囧的,还好前台十分人性化。。。。面试几分钟就出来了,自我感觉没什么问题,果然晚上就收到三面通知了。双喜临门,百田居然过了三面,明天让我再去面试,要不要啊,都已经三面了,还想怎样?不过今天误打误撞的提前了一天,果然是有先见之明,不然明天又撞车了。

    HANDLE hMutex   // handle to mutex

DWORD WINAPI GetCurrentThreadId();
这个与线程的出口参数lpThreadId是一致的;

                 WaitForSingleObject(hMutex, INFINITE);

    unsigned stack_size,

    HANDLE hHandle,        // handle to object

    DWORD dwThreadId;

#include <iostream>

2.线程的诞生与消亡

      HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL);

并发编程2.线程机制 20131030

 

    cout << “thread exit code is ” << dwExitCode <<
endl;

        

        
显示的线程操作以及替代方法:显示的如何去创建使用线程。这是编写并发软件的一种低层次的方式。对于线程的分析和管理是一项非常复杂的工作,他会分散我们对于实际问题的注意力。当然我们也会有一些其他的方式创建和使用线程。

 

        
IP获取下一条指令,对指令解码,将指令发射到处理器,然后递增IP或者是在遇到分支的时候或者函数调用的时候,对IP进行响应的调整。在执行代码时,程序的数据将在寄存器和主存之间来回移动。虽然在物理上来说,寄存器是在处理器上的,但是在某种程度上寄存器的状态同样也是属于线程的。如果线程因为一些因素被暂停执行,操作系统会将线程的状态保存在某一个内存块中,并且在随后当前线程恢复的时候再恢复过来。这些工作使得线程再次被执行的时候能够争取的获得指令等等操作,就好像线程没有被中断一样,将硬件中的线程状态信息保存下来之后再回复的过程就是上下文切换(Context
Switch).

DWORD WINAPI Fun(LPVOID lpParamter)

}

发表评论

电子邮件地址不会被公开。 必填项已用*标注