线程和进程

什么是进程什么是线程?

我的理解是进程是指在系统中正在运行的一个应用程序;程序一旦运行就是进程,或者更专业化来说:进程是指程序执行时的一个实例。
线程是进程的一个实体。
进程——资源分配的最小单位,线程——程序执行的最小单位。

线程进程的区别体现在几个方面:

第一:因为进程拥有独立的堆栈空间和数据段,所以每当启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这对于多进程来说十分“奢侈”,系统开销比较大,而线程不一样,线程拥有独立的堆栈空间,但是共享数据段,它们彼此之间使用相同的地址空间,共享大部分数据,比进程更节俭,开销比较小,切换速度也比进程快,效率高,但是正由于进程之间独立的特点,使得进程安全性比较高,也因为进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。一个线程死掉就等于整个进程死掉。

第二:体现在通信机制上面,正因为进程之间互不干扰,相互独立,进程的通信机制相对很复杂,譬如管道,信号,消息队列,共享内存,套接字等通信机制,而线程由于共享数据段所以通信机制很方便。。

3.属于同一个进程的所有线程共享该进程的所有资源,包括文件描述符。而不同过的进程相互独立。

4.线程又称为轻量级进程,进程有进程控制块,线程有线程控制块;

5.线程必定也只能属于一个进程,而进程可以拥有多个线程而且至少拥有一个线程;

第四:体现在程序结构上,举一个简明易懂的列子:当我们使用进程的时候,我们不自主的使用if else嵌套来判断pid,使得程序结构繁琐,但是当我们使用线程的时候,基本上可以甩掉它,当然程序内部执行功能单元需要使用的时候还是要使用,所以线程对程序结构的改善有很大帮助。

进程与线程的选择取决以下几点:

1、需要频繁创建销毁的优先使用线程;因为对进程来说创建和销毁一个进程代价是很大的。

2、线程的切换速度快,所以在需要大量计算,切换频繁时用线程,还有耗时的操作使用线程可提高应用程序的响应

3、因为对CPU系统的效率使用上线程更占优,所以可能要发展到多机分布的用进程,多核分布用线程;

4、并行操作时使用线程,如C/S架构的服务器端并发线程响应用户的请求;

5、需要更稳定安全时,适合选择进程;需要速度时,选择线程更好。

以上这一段来自https://www.cnblogs.com/renzhuang/articles/6733461.html

线程创建函数有beginthread,windows上有CreateThread函数。

DWORD WINAPI FucThread(LPVOID lpParam)
{
    while(true)
    {
        
    }
    return 1;
}

DWORD dwThread = 0;
HANDLE hThread = CreateThread(NULL,0,FucThread,0,0,&dwThread);

线程同步有4种方法:临界区(单进程内线程同步)、互斥量、事件对象(可对进程外的线程同步)、信号量(跨进程、可以指定允许个数)。

临界区用法:

CRITICAL_SECTION g_cri;
InitializeCriticalSection(&g_cri);  //初始化临界区
EnterCriticalSection(&g_cri); 
//.....操作......

LeaveCriticalSection(&g_cri);

互斥量用法:

HANDLE g_mutex;  //互斥量

g_mutex = CreateMutex(NULL, false, NULL); //创建互斥量
 WaitForSingleObject(g_mutex, INFINITE); 
//..............
ReleaseMutex(g_mutex);

事件用法:

HANDLE g_event = CreateEvent(NULL, false, false, NULL);
SetEvent(g_event);//刚开始要设成有信号状态

WaitForSingleObject(g_event, INFINITE);
//.......
SetEvent(g_event);

信号量用法:

HANDLE g_semaphore;
g_semaphore = CreateSemaphore(NULL, 1, 1, NULL);   //信号量
//第二个参数当前信号量,第三个参数最大信号量,当前信号量等于0时会造成堵塞。

WaitForSingleObject(g_semaphore, INFINITE);
//.........
ReleaseSemaphore(g_semaphore, 1, NULL);

 程序中比较实用的还有读写锁。在Windows平台,Vista和Server 2008及其更高的版本才开始提供读写锁相关的API,如果需要支持XP系统,那么往往需要自己实现读写锁机制。

注释:CreateEvent的第2个参数为true表示需要手动复原,第3个参数表示一开始就是有信号;其中m_sevent主要是为了不让写锁等读锁。如果连续几个读锁,等所有读锁好了之后在轮到写锁,明显不合理。

// 实现代码

class RWLock
{
public:
    RWLock();
    ~RWLock();
public:
    void AcquireReadLock();
    void ReleaseReadLock();
    void AcquireWriteLock();
    void ReleaseWriteLock();
private:
    volatile DWORD      m_cnt;
    CRITICAL_SECTION    m_cs;
    HANDLE              m_evt;
    HANDLE              m_sem;
};

RWLock::RWLock()
    : m_cnt(0)
    , m_evt(NULL)
    , m_cs(NULL)
    , m_sem(NULL)
{
    // 提倡的做法在专门的初始化函数里创建和初始化这些变量

    ::InitializeCriticalSection(&m_cs);

    // Event必须是手动重置,否则存在死锁隐患,即等待Event前,先被激活了
    m_evt = ::CreateEvent(NULL, TRUE, TRUE, NULL);
    m_sem = ::CreateSemaphore(NULL, 1, 1, NULL);
}

RWLock::~RWLock()
{
    ::CloseHandle(m_sem);
    ::CloseHandle(m_evt);
    ::DeleteCriticalSection(&m_cs);
}

void RWLock::AcquireReadLock()
{
    ::WaitForSingleObject(m_evt, INFINITE);

    ::EnterCriticalSection(&m_cs);
    if(0 == m_cnt++)
        ::WaitForSingleObject(m_sem, INFINITE);
    ::LeaveCriticalSection(&m_cs);
}

void RWLock::ReleaseReadLock()
{
    ::EnterCriticalSection(&m_cs);
    if(0 == --m_cnt)
        ::ReleaseSemaphore(m_sem, 1, NULL);
    ::LeaveCriticalSection(&m_cs);
}

void RWLock::AcquireWriteLock()
{
    ::ResetEvent(m_evt);
    ::WaitForSingleObject(m_sem, INFINITE);
}

void RWLock::ReleaseWriteLock()
{
    ::ReleaseSemaphore(m_sem, 1, NULL);
    ::SetEvent(m_evt);
}

// 使用示例

void Read()
{
    // 多个线程能够同时进行读操作

    rwLock.AcquireReadLock();

    // 读取数据
    
    rwLock.ReleaseReadLock();
}

void Write()
{
    rwLock.AcquireWriteLock();

    // 写入数据

    rwLock.ReleaseWriteLock();
}

进程间同步的方式有:进程间通信主要包括管道、系统IPC(包括消息队列、信号量、信号、共享内存等)、以及套接字socket。

  1、文件IO操作或者文件映射

  2、共享内存

#include <windows.h>
#include <iostream>
#include <string>
#include <cstring>
using namespace std;

int main()
{
    string strMapName("ShareMemory");                // 内存映射对象名称
    string strComData("This is common data!");        // 共享内存中的数据
    LPVOID pBuffer;                                    // 共享内存指针

    // 首先试图打开一个命名的内存映射文件对象  
    HANDLE hMap = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, 0, strMapName.c_str());
    if (NULL == hMap)
    {    // 打开失败,创建之
        hMap = ::CreateFileMapping(INVALID_HANDLE_VALUE,
                                   NULL,
                                   PAGE_READWRITE,
                                   0,
                                   strComData.length()+1,
                                   strMapName.c_str());
        // 映射对象的一个视图,得到指向共享内存的指针,设置里面的数据
        pBuffer = ::MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
        strcpy((char*)pBuffer, strComData.c_str());
        cout << "写入共享内存数据:" << (char *)pBuffer << endl;
    }
    else
    {    // 打开成功,映射对象的一个视图,得到指向共享内存的指针,显示出里面的数据
        pBuffer = ::MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
        cout << "读取共享内存数据:" << (char *)pBuffer << endl;
    }

    getchar();            // 注意,进程关闭后,所有句柄自动关闭,所以要在这里暂停

    // 解除文件映射,关闭内存映射文件对象句柄
    ::UnmapViewOfFile(pBuffer);
    ::CloseHandle(hMap);
    system("pause");        
    return 0;
}
View Code

  3、匿名管道(不能网络),用于父子进程间的通信。

  4、命名管道(网络)

   命名管道的命名规范与邮槽有些类似,对其标识也是采用的UNC格式:

\ServerPipe[Path]Name  

    其中,第一部分\Server指定了服务器的名字,命名管道服务即在此服务器创建,其字串部分可表示为一个小数点(表示本机)、星号(当前网络字段)、域名或是一个真正的服务;第二部分Pipe与邮槽的Mailslot一样是一个不可变化的硬编码字串,以指出该文件是从属于NPFS;第三部分[Path]Name则使应用程序可以唯一定义及标识一个命名管道的名字,而且可以设置多级目录。

  服务端接受消息

// vckzt.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include"cstdlib"
#include "windows.h"
#include "iostream"
using namespace std;
#define PIPE_NAME L"\\.\Pipe\test"

int _tmain(int argc, _TCHAR* argv[])
{
    char buffer[1024];
    DWORD ReadNum;

    HANDLE hPipe = CreateNamedPipe(PIPE_NAME, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, 1, 0, 0, 1000, NULL);
    if (hPipe == INVALID_HANDLE_VALUE)
    {
        cout << "创建命名管道失败!" << endl;
        CloseHandle(hPipe);
        return 0;
    }

    if (ConnectNamedPipe(hPipe, NULL) == FALSE)
    {
        cout << "与客户机连接失败!" << endl;
        CloseHandle(hPipe);
        return 0;
    }
    cout << "与客户机连接成功!" << endl;

    while (1)
    {
        if (ReadFile(hPipe, buffer, 1024, &ReadNum, NULL) == FALSE)
        {
            cout << "读取数据失败!" << endl;
            break;
        }

        buffer[ReadNum] = 0;
        cout << "读取数据:" << buffer << endl;
    }

    cout << "关闭管道!" << endl;
    CloseHandle(hPipe);

    system("Pause");
    return 0;
}
View Code

  客户端发送消息

// test2.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include"cstdlib"
#include "windows.h"
#include "iostream"
using namespace std;
#define PIPE_NAME L"\\.\Pipe\test"


int _tmain(int argc, _TCHAR* argv[])
{
    char buffer[1024];
    DWORD WriteNum;

    if (WaitNamedPipe(PIPE_NAME, NMPWAIT_WAIT_FOREVER) == FALSE)
    {
        cout << "等待命名管道实例失败!" << endl;
        return 0;
    }

    HANDLE hPipe = CreateFile(PIPE_NAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hPipe == INVALID_HANDLE_VALUE)
    {
        cout << "创建命名管道失败!" << endl;
        CloseHandle(hPipe);
        return 0;
    }
    cout << "与服务器连接成功!" << endl;
    while (1)
    {
        gets(buffer);//等待数据输入
        if (WriteFile(hPipe, buffer, strlen(buffer), &WriteNum, NULL) == FALSE)
        {
            cout << "数据写入管道失败!" << endl;
            break;
        }
    }

    cout << "关闭管道!" << endl;
    CloseHandle(hPipe);

    return 0;
}
View Code

  5、邮件槽(网络)

    优点:通过网络,将一条消息广播给一台或多台计算机。
    缺点:只允许从客户机到服务器,建立一种不可靠单向数据通信。不提供数据可靠性传播的保障。

  6、剪切板

  7、动态库,win32动态库的全局数据可以被调用该dll的所有进程共享

//动态库中
 #pragma data_seg("MyData")
     int g_Value=0;   //必须赋值,不然不能共享
 #pragma data_seg()    

  8、远程过程调用(RPC,网络)

  9、socket(网络)

  10、WM_COPYDATA的消息,实用sendmessage向目的窗口发送消息。

原文地址:https://www.cnblogs.com/jlyg/p/10360096.html