线程知识点汇总-Pthread和Win32Thread

线程概念

利用线程的意义

Advantages of using thread

  • thread reduce the OS overhead on creating and switching context compare with process
  • threads are tightly coupled therefore easy to share resources
  • thread improve performance by avoid waiting for processing or I/O
  • thread exploits the performance improvement on multiprocessor by concurrent

相比进程,线程的切换花销更小,可以更容易的共享资源,可以提高等待I/O和处理时的性能,还可以充分发掘多核处理器的计算能力。

线程中的全局变量-TLS

Basic understanding of thread

  • each thread has its own stack
  • the argument (pointer) pass to a thread at creation time is actually on thread's stack
  • thread can allocate TLS(thread local storage), which provides small pointer arrays to thread. TLS can only be accessed by the specific thread, which assures thread wouldn't modify one another's data

每个线程都有一个执行栈,在创建时通过void* 指针传入数据,每个线程还有一个局部堆,提供一系列指针,指向一块内存,仅由线程内的方法访问。

在WinThread中,

/*Allocates a thread local storage (TLS) index. Any thread of the process can subsequently use this index to store and retrieve values that are local to the thread, because each thread receives its own slot for the index.
the return value is a TLS index.*/
DWORD TlsAlloc();
/*Releases a thread local storage (TLS) index, making it available for reuse.*/
BOOL TlsFree(
  DWORD dwTlsIndex
);
/*Retrieves the value in the calling thread's thread local storage (TLS) slot for the specified TLS index. Each thread of a process has its own slot for each TLS index.*/
LPVOID TlsGetValue(
  DWORD dwTlsIndex
);
/*Stores a value in the calling thread's thread local storage (TLS) slot for the specified TLS index. Each thread of a process has its own slot for each TLS index.*/
BOOL TlsSetValue(
  DWORD  dwTlsIndex,
  LPVOID lpTlsValue
);
/*Thread local storage can be used in Dll to replace gloabl storage, each calling thread has its own global storage (for thread-safe DLL)*/

TLS对于实现线程安全的DLL有重要意义,用TLS代替全局变量,每一个调用DLL的会拥有一份静态内存。

C标准库中的线程安全问题

c lib was written to operate in single-threaded processes, some functions may use static storage to store intermediate results, therefore are not thread-safe when two separate threads simultaneously accessing and modifying static storgae.

早期的C标准库是用于单线程进程的,一些函数会利用静态内存存储中间数据。然而,这样的函数是不可重入的,线程内多个函数同时访问可能带来数据竞争。

/*Split string into tokens
A sequence of calls to this function split str into tokens, which are sequences of contiguous characters separated by any of the characters that are part of delimiters*/
char * strtok ( char * str, const char * delimiters );
/*scan a string to find the next occurrence of a token is an example, which maintains persistent state between successive function calls*/

例如strtok(),在前后两次调用中,函数内存在静态变量,它不是线程安全的。现在许多C标准库的实现已经是线程安全的。

线程状态

  • Running, executing on a processor
  • Wait, wait on a nonsignaled handle (thread, process, synchronization obj), blocked, sleeping
  • Ready, scheduler can put it in running state at any time
  • Suspend, a ready thread will not be run
  • Terminated

线程在其生命周期中,存在着五种状态,处于“Running”状态的线程正在某一个处理器上执行;“Wait”状态的线程正在被阻塞,等待某个信号;“Ready”状态的线程被放置在等待执行的队列中,随时可能被调度执行;“Suspend”状态的线程也在等待队列中,但是不会被执行;“Terminated”状态下,线程已经被终止,存在的的意义是为其它线程提供信息。

处理器亲和度

/*Sets a processor affinity mask for the specified thread.*/
DWORD_PTR SetThreadAffinityMask(
  HANDLE    hThread,
  DWORD_PTR dwThreadAffinityMask
);
/*Retrieves the process affinity mask for the specified process and the system affinity mask for the system.*/
BOOL GetProcessAffinityMask(
  HANDLE     hProcess,
  PDWORD_PTR lpProcessAffinityMask,
  PDWORD_PTR lpSystemAffinityMask
);
/*limit the processor that can run specific thread, and prevent other thread from using this processor*/

设置处理器亲和度可以使得线程被调度到特定的处理器核心执行,同时防止其它线程来竞争这个核心。

线程模型

Advantages of applying threading model when designing multi-thread program

  • model is well-understand and tested, which avoid many of the mistakes
  • model helps dev obtain the best performance
  • model correspond naturally to structures of programming problem
  • troubleshooting is easy if analyze in terms of models, underlying problem (race condition, deadlocks)is seen to violate the basic principles of models.

多线程对于编程带来了更多的复杂度,引入模型可以降低复杂度,帮助理清逻辑。

Pthread库介绍

Pthread是一种线程并发模型的实现,

  • Thread management functions to create, destroy, join, and detach threads.
  • Synchronization functions to manage the synchronization of threads, including mutex, condition variables, and barriers.

它提供了两方面的方法,线程管理和线程同步。本文以Pthread的方法汇总了线程和并发编程中的概念。

  • Pthread is provided by glibc, but in a separate library and requires explicit linkage
    the flag -pthread also setting certain preprocessor.

在Linux中,Pthread虽然由C标准库提供,但是在使用时需要指定链接Pthread库libthread。

gcc -Wall -Werror -pthread beard.c -o beard

Pthread线程管理

线程的创建和结束

创建线程时需要指定起始函数及其输入参数,pthread_attr_t默认为NULL,可以通过它指定线程栈大小,调度方法和初始状态。线程ID可以通过pthread_self()获取,由于线程ID的类型pthread_t在不同平台的实现不同,因此Pthread库实现pthread_equal()比较线程ID是否相等。

#include <pthread.h>

//pthread_attr_t object pointed defines the thread attributes such as
//  stack size, schedulizing parameters, initial detached state
int pthread_create(pthread_t *thread,
                   const pthread_attr_t *attr,
                   void *(*start_routine)(void *),
                   void *arg);
//obtain thread id at runtime
pthread_t pthread_self(void);
//equality operator for thread id
int pthread_equal(pthread_t t1, pthread_t t2);

和进程一样,线程的终结有三种方式,

thread termination
- return from start routine "fall off the end of main()"
- call pthread_exit()
- thread cancled by another thread via pthread_cancel()

process termination
- returns from its main()
- call exit()
- executes new binary image via execve()

即从起始函数返回,调用pthread_exit()或者由两个线程调用pthread_cancel()终结指定线程。

//terminate a thread deep in a function call stack,
// retval is provided to any thread waiting on the terminating thread’s death
void pthread_exit(void *retval);
//termination of threads by other threads cancellation, return 0 if success, but
//  since actual termination occurs asynchronously, the successful ret flag only
//  indicate cancellation request is processing successfully
int pthread_cancel(pthread_t thread);
//a thread's cancelable state can be enabled or disabled
int pthread_setcancelstate(int state, int *oldstate);
//A thread’s cancellation type is either asynchronous or deferred, asynchronous cancell may kill
//  thread at any point after request made, while deferred only kill thread at specific
//  cancellation points(safe), it's useful when leaving the thread in undefined state (e.g.
//  cancel thread in the middle of a critical region)
int pthread_setcanceltype(int type, int *oldtype);

一个线程可以通过pthread_setcancelstate()允许或者禁止其它线程终止它。线程调用pthread_cancel()通过线程ID终止线程,pthread_cancel()的过程是异步的,因此其返回0只代表终止请求成功发出,而不代表终止完成。线程被其它线程终止有两种模式,“异步地”和“延迟的”,前一种终止动作会在任何时候发生,后一种会延迟到一个安全的执行上下文,想象一下,线程在critical region中,持有锁的时候被结束会怎么样。

线程等待和分离

//allows one thread to block while waiting for the termination of another,
//  joining allows thread to synchronize their execution against the lifetime
//  of other threads,
//return EDEADLK when deadlock was detected
int pthread_join(pthread_t thread, void **retval);
//make the thread no longer joinable
int pthread_detach (pthread_t thread);

pthread_join(target_tid)使得调用线程被阻塞,直到目标线程完成执行。一个线程默认是可以加入等待的,调用pthread_detach()可以让线程分离执行,不再可以加入等待。

Pthread线程同步方法

同一个进程中的不同线程可以访问公共的进程地址空间,因此通过共享内存实现线程通信是一种常用的方法。然而,多个线程对共享内存的并发访问会带来数据访问的竞争。线程同步方法可以将并发访问串行化。下面对Pthread线程同步的方法和相关问题做一点小总结。

Mutex

Mutex,互斥锁用于保护共享数据,使其只被一个线程访问。当一个线程获得锁时,其它尝试获得锁的线程会被阻塞而睡眠,直至互斥锁被释放。

以下是mutex相关的API,mutex需要创建和释放,手动管理内部的动态内存。线程调用lock()去申请锁,调用unlock()释放锁。调用try_lock()的线程可以避免被阻塞,无法获得锁时可以进行处理。timedlock()可以为持有锁指定一段时间。

#include <pthread.h>
/* define and initialize a mutex named `mutex' */
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
    const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//Both return: 0 if OK, error number on failure
int pthread_mutex_lock(pthread_mutex_t *mutex);
//try to lock to avoid block (lock mutex confitionally), return EBUSY
//  if cannot lock
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict tsptr);
//All return: 0 if OK, error number on failure

一个简单的例子:[https://github.com/KevinACoder/apue.3e/blob/master/threads/mutex3.c]

Deadlock

锁使用不当容易造成死锁,例如,一个线程对一个mutex两次上锁;A线程拥有mutex1,B线程拥有mutex2,同时地,A线程尝试对mutex1上锁而B线程尝试对mutex2上锁;10个线程,每个线程完成后发送一个信号,需要等待11个信号才能完成工作。在这两种情形下,线程永远无法推进,造成死锁。

在锁的使用上避免死锁的方法有很多,例如,对于多个mutex,在多个线程中保持加锁的顺序一致;使用try_lock(),当线程无法上锁的话,释放线程所拥有的一些mutex锁,过一段时间在尝试上锁。

Read-Writer Mutex

Read-Writer Mutex, 读写锁和mutex的功能基本相同。不同的是,虽然写锁只有一个线程可以持有,但是读锁可以被多个线程持有。读写锁适用于保护数据结构,其读的频率高于写的频率。

#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
    const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
//Both return: 0 if OK, error number on failure
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
//All return: 0 if OK, error number on failure
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
//Both return: 0 if OK, error number on failure
int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock,
    const struct timespec *restrict tsptr);
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock,
    const struct timespec *restrict tsptr);
//Both return: 0 if OK, error number on failure

例子:[https://github.com/KevinACoder/apue.3e/blob/master/threads/rwlock.c]

Spin locks

Spin locks, 自旋锁,也是一种互斥锁,但是它们的效果有所不同,被mutex阻塞的线程会进入睡眠,而spin lock阻塞的线程会进行忙等待。不会睡眠意味着线程不会被重新调度,但是CPU资源也会被占用。因此,spin lock锁必须尽快释放。

自旋锁可以用于非抢占的内核代码中,被锁定的代码段不会被调度抢占,从而不会因为中断代码中加锁而出现死锁。在实时程序中,自旋锁可以避免线程被调度,即使线程的时间片已经用完,或出现更高优先级的执行线程。

#include <pthread.h>
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
int pthread_spin_destroy(pthread_spinlock_t *lock);
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);
//Both return: 0 if OK, error number on failure

Condition variable

Condition variable可以充当一个发令枪,一个线程发出信号,等待信号的线程不再被阻塞。

#include <pthread.h>
//init or free condition variable
int pthread_cond_init(pthread_cond_t *restrict cond,
    const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
//wait for a condition to be true.
int pthread_cond_wait(pthread_cond_t *restrict cond,
    pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
    pthread_mutex_t *restrict mutex,
    const struct timespec *restrict tsptr);
//signaling the thread or condition
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);    
//Both return: 0 if OK, error number on failure

例子:[https://github.com/KevinACoder/apue.3e/blob/master/threads/condvar.c]

Barriers

Barriers是一种同步机制,可以协调多个并行执行的线程。pthread_join()是一种Barriers,它让一个线程等待另一个线程结束。

#include <pthread.h>
int pthread_barrier_init(pthread_barrier_t *restrict barrier,
    const pthread_barrierattr_t *restrict attr,
    unsigned int count);
/*
 count: argument to specify the number of threads that must 
    reach the barrier
*/
int pthread_barrier_destroy(pthread_barrier_t *barrier);
//Both return: 0 if OK, error number on failure
int pthread_barrier_wait(pthread_barrier_t *barrier);
//ready to wait for all the other threads to catch up.
//  the calling thread is put to sleep if condition not yet saftisfied
//once barrier reached, it can be use again, but count is inmutalable
//Returns: 0 or PTHREAD_BARRIER_SERIAL_THREAD if OK, error number on failure

例子:[https://github.com/KevinACoder/apue.3e/blob/master/threads/barrier.c]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容

  • 接着上上节 thread ,本节主要介绍mutex的内容,练习代码地址。<mutex>:该头文件主要声明了与互斥量...
    jorion阅读 12,480评论 2 4
  • 接着上节 atomic,本节主要介绍condition_varible的内容,练习代码地址。本文参考http://...
    jorion阅读 8,477评论 0 7
  • 翻译:Synchronization 同步 应用程序中存在多个线程会导致潜在的问题,这些问题可能会导致从多个执行线...
    AlexCorleone阅读 2,469评论 0 4
  • 系统与网络编程 小作业 公交车停发车程序 线程 并发执行:看起来像同时运行,实际上在单核cpu里只有一个。将其排成...
    I踏雪寻梅阅读 451评论 0 3
  • 线程 操作系统线程理论 线程概念的引入背景 进程 之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有...
    go以恒阅读 1,635评论 0 6