线程,进程间的通讯和同步原理,实现用例和应用

简介: 线程/进程间的通讯方式 —使用全局变量/共享内存 —使用thread中的lParam参数 —使用socket —使用窗口和消息 —使用命名管道/匿名管道 —使用cmd参数 —使用environment变量 线程的启动,退出和lParam参数通讯 VC:...

线程/进程间的通讯方式

使用全局变量/共享内存
使用thread中的lParam参数
使用socket
使用窗口和消息
使用命名管道/匿名管道
使用cmd参数
使用environment变量

线程的启动,退出和lParam参数通讯

VC:

#include <windows.h>

DWORD WINAPI ThreadProc(LPVOID lParam);

DWORD dwThreadId;
HANDLE hThread = CreateThread(NULL, 0, ThreadProc, lParam, 0, &dwThreadId);

::TerminateThread(hThread, 0) //杀死线程,强烈不推荐使用!
::WaitForSingleObject(hThread, INFINITE) //等待线程退出
::CloseHandle(hThread); //不再关心该线程
::GetExitCodeThread (); //获取线程退出代码

.Net:

Using System.Threading;

Static void ThreadProc(Object lParam)

Object lParam = null;
Thread th = new Thread(new ParameterizedThreadStart(ThreadProc));
th.Start(lParam);

th.IsBackground = true; // 主循环结束后依靠.Net运行机制自动取消该线程
th.Join(); //等待线程退出
th.Abort(); //杀死线程

进程的启动,退出,命令行和环境变量

VC:

#include <windows.h>

STARTUPINFO si = sizeof(STARTUPINFO) };
PROCESS_INFORMATION ps;

Char* pFileName = …;
Char* pArgs = …;
LPVOID pEnv = NULL;
BOOL bRet = CreateProcess(pFileName, pArgs, …, pEnv, NULL, &si, &ps);

::TerminateProcess(ps.hProcess, 0) //杀死进程,不推荐使用,但比TerminateThread强很多。
::WaitForSingleObject(ps.hProcess, INFINITE) //等待进程退出
::CloseHandle(ps.hProcess); // 不再关心该进程
::GetExitCodeProcess(); //获取进程退出代码

.Net:

Using System.Diagnotics;

Process p = new Process();
p.StartInfo = new ProcessStartInfo();
p.StartInfo.FileName = …;
p.StartInfo.Arguments = …;
p.StartInfo.EnvironmentVariables = …;
p.Start();

p.WaitForExit(); //等待进程退出
p.Kill(); //杀死进程
p.ExitCode // 获取进程退出代码

管道通讯

创建匿名管道(读管道和写管道)
BOOL WINAPI CreatePipe(
		PHANDLE hReadPipe,  
		PHANDLE hWritePipe, 
		LPSECURITY_ATTRIBUTES lpPipeAttributes,
		DWORD nSize); 

销毁匿名管道
BOOL WINAPI CloseHandle(
		HANDLE hPipe
	);

管道通讯用例NPSample

共享内存(VC)

CreateFileMapping
MapViewOfFile
UnmapViewOfFile
CloseHandle
共享内存实现用例ShmSample

线程 / 进程间的同步

有了以上进程通讯的方式,必然会产生同步问题。
同步的几种方式:
	临界区CriticalSection --- 轻量级代码关键段
	互斥锁Mutex --- 互斥锁,只能被一个进程拥有
	信号量Semaphore  --- 信号灯,
   事件Event    ---- 一次性手动或自动事件
   原子变量Atomic   ---- 保证一个变量值唯一性
   自旋锁SpinLock  ---- 用户态自旋锁,适合短代码加锁
   
   都可以跨进程使用,但临界区跨进程必须放于共享内存中。

CriticalSection

VC:

#include <windows.h>

CRITICAL_SECTION sec;

InitializeCriticalSection(&sec); //初始化临界区
InitializeCriticalSectionAndSpinCount(&sec, 2000); //自旋方式初始化临界区
DeleteCriticalSection(&sec); //删除临界区
EnterCritcalSection(&sec); //进入临界区
LeaveCriticalSection(&sec); //离开临界区
TryEnterCriticalSection(&sec); //试图进入临界区,进入成功要离开,否则返回FALSE

注意:尽量使用类锁(自动析构)如有性能或者防止死锁需要,尽量不使用return,防止离开时忘记释放锁

.Net:

String s;
lock (s) // 锁定对象s
{
    ….;
}

System.Threading.Motitor.Enter(s);

System.Threading.Motitor.Leave(s); //尽量加到finally块里面,避免抛出异常导致锁未释放

Mutex

VC:

#include <windows.h>

HANDLE mutex;

mutex = CreateMutex(NULL,…); //初始化匿名互斥锁
Mutex = CreateMutex(“123”, …); //初始化有名称互斥锁
Mutex = OpenMutex(“123”, …); //打开有名称互斥锁
CloseHandle(mutex); //关闭互斥锁;
WaitForSingleObject(mutex, waitTime); // 等待互斥锁,第二个参数为等待时间
ReleaseMutex(mutex); //释放互斥锁;
注意:尽量使用类锁(自动析构)如有性能或者防止死锁需要,尽量不使用return,防止离开时忘记释放锁
注意:线程/进程退出时互斥锁将自动被释放,其他等待的线程/进程将获得该锁并返回WAIT_ABANDONED.

.Net:

Using System.Threading;

Mutex m = new Mutex();
Mutex m = new Mutex(“123”, …); //初始化有名称互斥锁
m.WaitOne(); // 等待互斥锁
m.ReleaseMutex(); // 释放互斥锁,尽量加到finally块里面,避免抛出异常导致锁未释放

Semaphore

VC:

#include <windows.h>

HANDLE sem;

sem = CreateSemaphore(NULL,…); //初始化匿名信号量并初始化初始信号数量
sem = CreateSemaphore(“123”, …); //初始化有名称信号量
sem = OpenSemaphore(“123”, …); //打开有名称信号量
CloseHandle(sem); //关闭信号量;
WaitForSingleObject(sem, waitTime); // 等待信号量,第二个参数为等待时间,若成功,信号量计数-1
ReleaseSemaphore(sem, count); //将信号量计数增加count;
注意:尽量使用类锁(自动析构)如有性能或者防止死锁需要,尽量不使用return,防止离开时忘记释放锁
注意:线程/进程退出时信号量将不会被释放,其他等待的线程/进程将依然会锁住

.Net:

Using System.Threading;

Semaphore m = new Semaphore();
Semaphore m = new Semaphore(“123”, …); //初始化有名称信号量
m.WaitOne(); // 等待信号量并将信号量数目-1
m.Release (count); // 信号量计数增加count,尽量加到finally块里面,避免抛出异常导致锁未释放

利用 Semaphore 实现控制队列

Semaphore和queue一起用,可完成完整的加锁队列,
消费者每次取数据时等待信号量,等待成功后取数据(加锁)。
生产者每次生产数据时先将数据入队(加锁),并将信号量计数+1。

Event

VC:

#include <windows.h>

HANDLE eve;

eve = CreateEvent(NULL,…); //初始化匿名事件并初始化状态和工作方式
eve = CreateEvent(“123”, …); //初始化有名称事件
eve = OpenEvent(“123”, …); //打开有名称事件
CloseHandle(eve); //关闭事件;
WaitForSingleObject(eve, waitTime); // 等待事件,第二个参数为等待时间,若成功,如工作方式为自动重置,事件将自动被重置
SetEvent(eve); // 设置事件为有信号状态
ResetEvent(eve); // 设置事件为无信号状态
注意:尽量使用类锁(自动析构)如有性能或者防止死锁需要,尽量不使用return,防止离开时忘记释放锁
注意:线程/进程退出时信号量将不会被释放,其他等待的线程/进程将依然会锁住

.Net:

Using System.Threading;

EventHandle m = new EventHandle ();
EventHandle m = new EventHandle (“123”, …); //初始化有名称信号量
m.WaitOne(); // 等待信号量并将信号量数目-1
m.Release (count); // 信号量计数增加count,尽量加到finally块里面,避免抛出异常导致锁未释放

利用Event控制线程运行(VC)

看过很多线程代码是这样写的
DWORD WINAPI ThreadProc(LPVOID param)
{
	while (m_bRun)
	{
		DoWork(…);
		Sleep(1);
       }
}
Void Stop()
{
	m_bRun = FALSE;
	::WaitForSingleObject(…);
}

实际应该这样优化:
DWORD WINAPI ThreadProc(LPVOID param)
{
	while (TRUE)
	{
	DWORD dwRet = WaitForSingleObject(hEvent, 	1);
	if (dwRet == WAIT_OBJECT_0)
		break;
	DoWork(…);
	Sleep(1);
       }
}

Void Stop()
{
	SetEvent(hEvent);
	::WaitForSingleObject(…);
}

原子变量

原子变量的原理

原子变量的原理是利用硬件支持锁定某块内存的功能实现,就算有多个CPU同时访问该段内存,也只有一个能进入该内存,其他CPU将被锁住。

由于原子变量并非对代码段加锁,而是对数据区加锁,并且锁的空间很小,因此一般只适合数量上的(引用计数)或者数值上的(个数,次数)的加锁。

VC: InterlockedIncrement, InterlockedExchangeAdd, InterlockedDecrement, …

.Net: System.Threading.Interlocked类


自旋锁

自旋锁是用户态锁,利用锁定某块内存的方式不断读取该块内存数据来加锁/解锁,工作机理和原子变量类似,但要注意自旋锁仅适合非单核CPU(单核在用户态自旋是没有意义的)和较短代码段的锁,若锁的时间过长将引起大量的CPU耗损。

同步与死锁

死锁原因
忘记在某个地方释放锁
使用TerminateThread/TerminateProcess导致锁对象未释放
加锁未按顺序加锁
锁太多,不知该如何加锁
每个锁的锁周期要短,不要对非关键代码区域段加锁
每个锁的目的要明确,不要一个锁去锁太多的对象和元素
加锁要按顺序加锁
注意SendMessage类函数和回调函数的加锁,确保在调用之前已经释放了应该释放的锁

目录
相关文章
|
28天前
|
调度 开发者 Python
深入浅出操作系统:进程与线程的奥秘
在数字世界的底层,操作系统扮演着不可或缺的角色。它如同一位高效的管家,协调和控制着计算机硬件与软件资源。本文将拨开迷雾,深入探索操作系统中两个核心概念——进程与线程。我们将从它们的诞生谈起,逐步剖析它们的本质、区别以及如何影响我们日常使用的应用程序性能。通过简单的比喻,我们将理解这些看似抽象的概念,并学会如何在编程实践中高效利用进程与线程。准备好跟随我一起,揭开操作系统的神秘面纱,让我们的代码运行得更加流畅吧!
|
27天前
|
消息中间件 Unix Linux
【C语言】进程和线程详解
在现代操作系统中,进程和线程是实现并发执行的两种主要方式。理解它们的区别和各自的应用场景对于编写高效的并发程序至关重要。
50 6
|
28天前
|
调度 开发者
深入理解:进程与线程的本质差异
在操作系统和计算机编程领域,进程和线程是两个核心概念。它们在程序执行和资源管理中扮演着至关重要的角色。本文将深入探讨进程与线程的区别,并分析它们在现代软件开发中的应用和重要性。
55 5
|
26天前
|
算法 调度 开发者
深入理解操作系统:进程与线程的管理
在数字世界的复杂编织中,操作系统如同一位精明的指挥家,协调着每一个音符的奏响。本篇文章将带领读者穿越操作系统的幕后,探索进程与线程管理的奥秘。从进程的诞生到线程的舞蹈,我们将一起见证这场微观世界的华丽变奏。通过深入浅出的解释和生动的比喻,本文旨在揭示操作系统如何高效地处理多任务,确保系统的稳定性和效率。让我们一起跟随代码的步伐,走进操作系统的内心世界。
|
28天前
|
调度 开发者
核心概念解析:进程与线程的对比分析
在操作系统和计算机编程领域,进程和线程是两个基本而核心的概念。它们是程序执行和资源管理的基础,但它们之间存在显著的差异。本文将深入探讨进程与线程的区别,并分析它们在现代软件开发中的应用和重要性。
51 4
|
1月前
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
|
1月前
|
Java
java小知识—进程和线程
进程 进程是程序的一次执行过程,是系统运行的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程 线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比
29 1
|
1月前
|
监控 JavaScript 前端开发
python中的线程和进程(一文带你了解)
欢迎来到瑞雨溪的博客,这里是一位热爱JavaScript和Vue的大一学生分享技术心得的地方。如果你从我的文章中有所收获,欢迎关注我,我将持续更新更多优质内容,你的支持是我前进的动力!🎉🎉🎉
25 0
|
5月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能
|
5月前
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
197 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)