多线程应用程序并行性
通常,在多线程应用程序中有两种相互关联但不同的现象:并发性和并行性。
并发是两个或多个线程在执行中重叠的能力。
并行性是同时执行两个或多个线程的能力。
正是并发性导致了流式处理中的大多数复杂性 - 线程可以以不可预测的顺序执行。在线程共享资源的情况下,这无疑会导致竞争条件。
术语争用条件通常是指对两个和多个线程的共享资源的不同步访问导致错误的程序行为的情况。
让我们看一个比赛的例子。
如今,很难想象我们没有塑料卡的生活。ATM取款很久以前就成为日常工作:插入卡,输入PIN码和所需的金额。如果成功完成,我们将收到计划的现金金额。反过来,银行需要通过以下算法验证资金是否可用:
银行账户上是否至少有 X 个单位的可用货币?
如果是,将帐户余额减少 X 值,向用户分配 X 个货币单位。
否则,将生成错误消息。
具有争用条件的代码示例:
int cash_out(struct account *ac, int amount) {
const int balance = ac->balance;
if (balance < amount)
return -1;
ac->balance = balance - amount;
discard_money_routine(amount);
return 0;
}
当在线支付购买并“同时”从 ATM 提取现金时,可能会出现种族。
为了避免比赛,有必要对代码进行以下更新:
int cash_out(struct account *ac, int amount) {
lock();
const int balance = ac->balance;
if (balance < amount)
return -1;
ac->balance = balance - amount;
unlock();
discard_money_routine(amount);
return 0;
}
在Windows操作系统中,需要独占访问某些共享数据的代码区域称为“关键部分”。
用于处理关键部分的结构类型为CRITICAL_SECTION。让我们回顾一下它的字段:
typedef struct _RTL_CRITICAL_SECTION {
PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
//
// The following three fields control entering and exiting the critical
// section for the resource
//
LONG LockCount;
LONG RecursionCount;
HANDLE OwningThread; // from the thread's ClientId->UniqueThread
HANDLE LockSemaphore;
ULONG_PTR SpinCount; // force size on 64-bit systems when packed
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
尽管CRITICAL_SECTION正式不属于未记录的结构,但微软仍然认为用户无需了解其组织。实际上,它是一种黑匣子。要使用此结构,无需直接使用其字段,而只需通过 Windows 函数,将此结构的相应实例的地址传递给它们。
CRITICAL_SECTION结构通过以下调用初始化:
void 初始化关键部分(PCRITICAL_SECTION 个);
如果我们知道不再需要CRITICAL_SECTION结构,那么我们可以借助以下调用将其删除:
无效删除关键部分(PCRITICAL_SECTION个);
使用共享资源的代码区域应先进行以下调用:
无效的进入临界部分(PCRITICAL_SECTION个);
我们可以使用以下命令代替EnterCriticalSection:
bool 尝试输入关键部分(PCRITICAL_SECTION 个);
TryEnterCriticalSection允许线程检查资源可访问性,并在无法访问时参与另一个活动。在成功的情况下(函数返回TRUE),很明显结构元素已更新,资源已锁定。
在使用共享资源的代码区域末尾,应始终存在以下调用:
void LeaveCriticalSection(PCRITICAL_SECTION pcs);
LeaveCriticalSection检查CRITICAL_SECTION结构元素,并将资源锁定计数器 (LockCount) 减少 1。
类似于 Linux 操作系统中的CRITICAL_SECTION是可变互斥pthread_mutex_t。在使用之前,需要初始化此变量 – 写入常量PTHREAD_MUTEX_INITIALIZER的值或调用pthread_mutex_init函数。
#include
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
要使用默认属性值初始化互斥锁,必须将NULL传递给attr属性。可以在帮助页面上找到特定的互斥锁属性值。
可以通过以下调用删除互斥锁:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
通过调用pthread_mutex_lock函数锁定互斥锁:
int pthread_mutex_lock(pthread_mutex_t *mutex);
如果互斥锁已被锁定,则调用线程将被阻塞,直到释放互斥锁。互斥锁在pthread_mutex_unlock功能的帮助下解锁:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
如果我们想检查资源的可访问性,那么我们可以使用pthread_mutex_trylock函数:
int pthread_mutex_trylock(pthread_mutex_t *mutex);
如果互斥锁被锁定,上述函数将返回EBUSY。
所有用于处理互斥锁的函数在成功时返回 0,在失败时返回错误代码。
让我们总结一下。在 Windows 操作系统中,要使用共享资源,必须使用关键部分和特殊类型的CRITICAL_SECTION。在 Linux 操作系统中,我们可以出于相同目的使用pthread_mutex_t类型的互斥体。
同步功能记录在表 4 中。
窗口函数 | Linux函数 |
初始化关键部分 | pthread_mutex_init() |
进入关键部分 | pthread_mutex_lock() |
离开关键部分 | pthread_mutex_unlock() |
尝试进入关键部分 | pthread_mutex_trylock() |
删除关键部分 | pthread_mutex_destroy() |
表 4.共享资源的同步功能。
螺纹端接
在实践中,需要编写线程终止的情况之一是海量数据处理。当主线程向所有线程发出退出信号,但其中一个线程仍在处理信息时,可能会出现这种情况。如果与信息丢失相比,及时性是应用程序性能的更高优先级因素,则需要退出线程并释放系统资源。本节将介绍退出线程的方法。
线程可以通过以下方式退出:
线程函数返回
线程调用 ExitThread 函数
进程的任何线程都调用 TerminateThread 函数
进程的任何线程都调用 ExitProcess 函数
让我们仔细看看其中的每一个。
线程函数返回。
干净代码的一个很好的例子是设计线程函数,以便线程仅在函数返回后终止。在 Windows 操作系统中,这种线程终止方式保证正确清理线程拥有的资源。在 Linux 操作系统中,在线程可连接的情况下,必须调用其中一个连接函数。在一般情况下,会发生以下情况:
系统正确释放线程占用的资源。
系统设置线程退出代码。
此内核对象 «thread» 的用户计数器减少 1。
在 Windows 操作系统中,可以通过调用以下内容来强制终止线程:
void ExitThread(DWORD dwExitCode);
线程退出代码值将添加到dwExitCode参数中。很容易注意到该函数没有返回值,因为在调用该函数后,线程将不复存在。
在Linux操作系统中,有一个完整的ExitThread模拟:
void pthread_exit(void *rval_ptr);
参数rval_ptr表示包含返回值的非类型指针。此指针可由调用pthread_join函数的其他进程线程获取。
函数调用pthread_join将线程带到分离状态。此状态允许赢回线程资源。如果线程已处于分离状态,则调用pthread_join的线程将收到ESRCH错误代码。有时,当使用第二个非 NULL参数调用pthread_join时,可能会输出分段错误错误。
进程的任何线程都调用 TerminateThread 函数。
一个线程可以传递请求以强制终止同一进程中的另一个线程。在Windows操作系统中,这是在以下功能的帮助下组织的:
bool TerminateThread(
HANDLE hThread,
DWORD dwExitCode
);
上述函数从任何其他线程终止了 hThread线程。您可以向dwExitCode参数添加一个值,系统将视之为线程退出代码。线程被杀死后,此内核对象 «thread» 的用户计数器将减少 1。
在 Linux 操作系统中,当一个线程可以通过调用pthread_cancel函数传递强制终止同一进程中另一个线程的请求时,可以实现类似的功能:
int pthread_cancel(pthread_t tid);
此函数需要与pthread_setcancelstate和pthread_setcanceltype函数结合使用。如果使用pthread_cancel,rval_ptr将被PTHREAD_CANCELED。
让我们仔细看看erminateThread和 Linux 操作系统中的类似操作:
#ifdef __PL_WINDOWS__
BOOL bret = FALSE;
bret = TerminateThread(h, x);
#endif //__PL_WINDOWS__
#ifdef __PL_LINUX__
int iret = 0, bret;
iret = syscall(SYS_tkill,tid, 0);
if (iret == 0) {
iret = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
if (iret != 0) {
bret = FALSE;
}
else {
iret = pthread_cancel(h);
if (iret == 0 || iret == ESRCH) {
bret = TRUE;
} else {
wait_thread:
clock_gettime(CLOCK_REALTIME, &wait_time);
ADD_MS_TO_TIMESPEC(wait_time, 1000); //1000 ms
iret = pthread_timedjoin_np(h, NULL, &wait_time);
switch (iret) {
case 0:
bret = TRUE;
break;
case ETIMEDOUT:
if (retries_count++ < 5) // 5 Attempts
{
goto wait_thread;
}
bret = FALSE;
break;
default:
bret = FALSE;
break;
}
}
(void)pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
}
}
else {
bret = TRUE;
}
#endif //__PL_LINUX__
审核编辑:郭婷
全部0条评论
快来发表一下你的评论吧 !