共享内存编程
进程通过共享内存空间相互通信或协同工作,所有进程都可以访问该共享内存空间,比传递消息更快更高效
pthread
posix标准指定用于跨unix系统的可移植性
pthread是现成posix标准的实现
创建pthread
pthread_create(thread,attr,routine,arg)
- thread:新线程的唯一标识符
- attr:用于设置线程属性。默认值为NULL
- routine:创建线程后将执行的C例程。
- arg:可以传递给start_routine的单个参数 。它必须作为空类型的指针强制转换通过引用传递。如果不传递任何参数,则可以使用NULL。
#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS 5
void* PrintHello(void* threadId) {
printf("Hello World! It's me, thread #!\n");
pthread_exit(NULL);
return 0;
}
int main(int argc, char* argv[]) {
pthread_t threads[NUM_THREADS];
for (long tid = 0; tid < NUM_THREADS; tid++) {
pthread_create(&threads[tid], NULL, PrintHello, (void*)&tid);
}
}
pthread_join(threadId, status)
- 阻止进行,指导指定的线程终止
- 将指定线程的返回值取回
- 当status的值为NULL时仅执行同步操作,使用for循环来规定不同的线程
pthread_detach(threadId)
- 分离线程,一旦分离就无法连接了
- 分离线程可以释放一些系统资源,防止造成资源的浪费
pthread_exit()
终止例程
pthread_attr_t pthread_attr_init()
pthread_attr_setdetachstate()
pthread_attr_destroy()
创建线程时,其属性之一定义它是可连接的还是可分离的。只有创建为可连接的线程才能被连接。如果线程创建为分离线程,则永远无法加入。
POSIX标准的最终草案指定应将线程创建为可连接的。
要显式地创建可连接或可分离的线程,请使用pthread_create()例程中的 attr参数。典型的4步骤过程是:
1.声明pthread_attr_t 数据类型 的pthread属性变量
2.用pthread_attr_init()初始化属性变量
3.使用pthread_attr_setdetachstate()设置属性分离状态
4.完成后,释放带pthread_attr_destroy()属性所使用的库资源
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define NUM_THREADS 4
void* BusyWork(void* t)
{
int i;
long tid;
double result = 0.0;
tid = (long)t;
printf("Thread %ld starting...\n", tid);
for (i = 0; i < 1000000; i++)
{
result = result + sin(i) * tan(i);
}
printf("Thread %ld done. Result = %e\n", tid, result);
pthread_exit((void*)t);
return 0;
}
int main(int argc, char* argv[])
{
pthread_t thread[NUM_THREADS];
pthread_attr_t attr;
int rc;
long t;
void* status;
/* Initialize and set thread detached attribute */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
for (t = 0; t < NUM_THREADS; t++) {
printf("Main: creating thread %ld\n", t);
rc = pthread_create(&thread[t], &attr, BusyWork, (void*)t);
if (rc) {
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(-1);
}
}
/* Free attribute and wait for the other threads */
pthread_attr_destroy(&attr);
for (t = 0; t < NUM_THREADS; t++) {
rc = pthread_join(thread[t], &status);
if (rc) {
printf("ERROR; return code from pthread_join() is %d\n", rc);
exit(-1);
}
printf("Main: completed join with thread %ld having a status of % ld\n", t, (long)status);
}
printf("Main: program completed. Exiting.\n");
pthread_exit(NULL);
}
pthread_self()
返回调用线程的系统为线程分配的唯一id
堆栈管理
pthread_attr_getstacksize(attr,stacksize)
pthread_attr_setstacksize(attr,stacksize)
POSIX标准不规定线程堆栈的大小。这取决于实现方式并且有所不同。
超过默认堆栈限制通常很容易做到,并且具有通常的结果:程序终止和/或损坏的数据。
安全和可移植的程序不依赖于默认的堆栈限制,而是使用pthread_attr_setstacksize例程为每个线程显式分配足够的堆栈 。
的pthread_attr_getstackaddr和 pthread_attr_setstackaddr例程可以通过,其中一个线程堆栈必须放置在存储器的某个特定区域中的环境的应用中使用。
#include <pthread.h>
#include <stdio.h>
#define NTHREADS 4
#define N 1000
#define MEGEXTRA 1000000
pthread_attr_t attr;
void* dowork(void* threadid)
{
double A[N][N];
int i, j;
long tid;
size_t mystacksize;
tid = (long)threadid;
pthread_attr_getstacksize(&attr, &mystacksize);
printf("Thread %ld: stack size = %li bytes \n", tid, mystacksize);
for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
A[i][j] = ((i * j) / 3.452) + (N - i);
pthread_exit(NULL);
return 0;
}
int main(int argc, char* argv[])
{
pthread_t threads[NTHREADS];
size_t stacksize;
int rc;
long t;
pthread_attr_init(&attr);
pthread_attr_getstacksize(&attr, &stacksize);
printf("Default stack size = %li\n", stacksize);
stacksize = sizeof(double) * N * N + MEGEXTRA;
printf("Amount of stack needed per thread = %li\n", stacksize);
pthread_attr_setstacksize(&attr, stacksize);
printf("Creating threads with stack size = %li bytes\n", stacksize);
for (t = 0; t < NTHREADS; t++) {
rc = pthread_create(&threads[t], &attr, dowork, (void*)t);
if (rc) {
printf("ERROR; return code from pthread_create() is %d\n", rc);
//exit(-1);
}
}
printf("Created %ld threads.\n", t);
pthread_exit(NULL);
}
互斥变量
Mutex是“互斥”的缩写。互斥变量是实现线程同步和在发生多次写入时保护共享数据的主要方法之一。
互斥变量就像“锁”一样,保护对共享数据资源的访问。Pthread中使用的互斥锁的基本概念是,在任何给定时间,只有一个线程可以锁定(或拥有)互斥锁变量。因此,即使多个线程尝试锁定互斥锁,也只有一个线程会成功。在拥有线程解锁该互斥锁之前,没有其他线程可以拥有该互斥锁。线程必须“轮流”访问受保护的数据。
互斥体可用于防止出现“竞赛”情况。涉及银行交易的竞争条件示例如下所示:
在上面的示例中,当线程正在使用此共享数据资源时,应该使用互斥锁来锁定“平衡”。
拥有互斥量的线程执行的动作通常是全局变量的更新。这是一种安全的方法,可确保当多个线程更新同一变量时,最终值与仅一个线程执行更新时的最终值相同。被更新的变量属于“关键部分”。
使用互斥锁的典型顺序如下:
- 创建并初始化互斥变量
- 多个线程试图锁定互斥锁
- 只有一个成功,并且该线程拥有互斥量
- 所有者线程执行一些操作
- 所有者解锁互斥锁
另一个线程获取互斥锁并重复该过程
最终互斥体被破坏
当多个线程竞争一个互斥锁时,失败者将在该调用时阻塞-通过“ trylock”而不是“ lock”调用可以进行无阻塞调用。
保护共享数据时,程序员有责任确保每个需要使用互斥锁的线程都这样做。例如,如果4个线程正在更新相同的数据,但是只有一个线程使用互斥锁,则数据仍可能被破坏。
pthread_mutex_init (mutex,attr)
pthread_mutex_destroy (mutex)
互斥变量必须使用pthread_mutex_t类型声明,并且必须先初始化才能使用。有两种初始化互斥量变量的方法:
静态,在声明时。例如:
pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;
使用pthread_mutex_init()例程动态地。此方法允许设置互斥对象属性attr。
pthread_mutex_destroy()释放不再需要的互斥对象。
pthread_mutex_lock (mutex)
pthread_mutex_trylock (mutex)
pthread_mutex_unlock (mutex)
pthread_mutex_lock()的例程用于由一个线程以获取在指定的锁定互斥变量。如果互斥锁已经被另一个线程锁定,则此调用将阻塞调用线程,直到互斥锁被解锁为止。
pthread_mutex_trylock()将尝试锁定互斥锁。但是,如果互斥锁已被锁定,则例程将立即返回“忙”错误代码。如在优先级倒置的情况下,此例程可能对防止死锁情况很有用。
如果由拥有线程调用,则pthread_mutex_unlock()将解锁互斥锁。如果其他线程要获取互斥量以使用受保护的数据,则在线程完成其对受保护数据的使用之后需要调用此例程。如果出现以下情况,将返回错误:
如果互斥锁已经解锁
如果互斥锁由另一个线程拥有
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct
{
double* a;
double* b;
double sum;
int veclen;
} DOTDATA;
#define NUMTHRDS 4
#define VECLEN 100
DOTDATA dotstr;
pthread_t callThd[NUMTHRDS];
pthread_mutex_t mutexsum;
void* dotprod(void* arg)
{
int i, start, end, len;
long offset;
double mysum, * x, * y;
offset = (long)arg;
len = dotstr.veclen;
start = offset * len;
end = start + len;
x = dotstr.a;
y = dotstr.b;
mysum = 0;
for (i = start; i < end; i++)
{
mysum += (x[i] * y[i]);
}
pthread_mutex_lock(&mutexsum);
dotstr.sum += mysum;
pthread_mutex_unlock(&mutexsum);
pthread_exit((void*)0);
return 0;
}
int main(int argc, char* argv[])
{
long i;
double* a, * b;
void* status;
pthread_attr_t attr;
a = (double*)malloc(NUMTHRDS * VECLEN * sizeof(double));
b = (double*)malloc(NUMTHRDS * VECLEN * sizeof(double));
for (i = 0; i < VECLEN * NUMTHRDS; i++)
{
a[i] = 1.0;
b[i] = a[i];
}
dotstr.veclen = VECLEN;
dotstr.a = a;
dotstr.b = b;
dotstr.sum = 0;
pthread_mutex_init(&mutexsum, NULL);
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
for (i = 0; i < NUMTHRDS; i++)
{
pthread_create(&callThd[i], &attr, dotprod, (void*)i);
}
pthread_attr_destroy(&attr);
for (i = 0; i < NUMTHRDS; i++)
{
pthread_join(callThd[i], &status);
}
printf("Sum = %f \n", dotstr.sum);
free(a);
free(b);
pthread_mutex_destroy(&mutexsum);
pthread_exit(NULL);
}
条件变量
pthread_cond_init (condition,attr)
pthread_cond_destroy (condition)
条件变量必须使用pthread_cond_t类型声明,并且必须先进行初始化,然后才能使用它们。有两种方法可以初始化条件变量:
静态,在声明时。例如:
pthread_cond_t myconvar = PTHREAD_COND_INITIALIZER;
使用pthread_cond_init()例程动态地。通过条件参数将创建的条件变量的ID返回给调用线程。此方法允许设置条件变量对象属性 attr。
应该使用pthread_cond_destroy()释放不再需要的条件变量。
pthread_cond_wait (condition,mutex)
pthread_cond_signal (condition)
pthread_cond_broadcast (condition)
pthread_cond_wait()阻塞调用线程,直到发出指定条件为止。此例程应在互斥锁锁定时调用,并且它将在等待时自动释放互斥锁。接收到信号并唤醒线程后,互斥锁将自动锁定以供线程使用。线程完成后,程序员负责解锁互斥量。
建议:使用WHILE循环而不是IF语句(请参见下面的示例中的watch_count例程)来检查等待的条件,可以帮助解决一些潜在的问题,例如:
- 如果多个线程正在等待相同的唤醒信号,则它们将轮流获取互斥对象,然后它们中的任何一个都可以修改它们都等待的条件。
- 如果线程由于程序错误收到错误的信号
- 允许Pthreads库在不违反标准的情况下向等待的线程发出虚假唤醒。
pthread_cond_signal()例程用于信号(或唤醒),其条件变量等待另一个线程。它应该在互斥锁锁定后调用,并且必须解锁互斥锁才能完成pthread_cond_wait()例程。
pthread_cond_broadcast()例程应该被用来代替 调用pthread_cond_signal()如果多于一个线程处于阻塞等待状态。
- 在调用pthread_cond_wait()之前先调用pthread_cond_signal()是逻辑错误。
- 在调用pthread_cond_wait()之前未锁定互斥锁 可能导致其未阻塞。
- 调用pthread_cond_signal()后未能解锁互斥锁
- 可能无法完成匹配的pthread_cond_wait()例程(它将保持阻塞状态)。
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS 3
#define TCOUNT 10
#define COUNT_LIMIT 12
int count = 0;
int thread_ids[3] = { 0,1,2 };
pthread_mutex_t count_mutex;
pthread_cond_t count_threshold_cv;
void* inc_count(void* t)
{
int i;
long my_id = (long)t;
for (i = 0; i < TCOUNT; i++) {
pthread_mutex_lock(&count_mutex);
count++;
if (count == COUNT_LIMIT) {
pthread_cond_signal(&count_threshold_cv);
printf("inc_count(): thread %ld, count = %d Threshold reached.\n",
my_id, count);
}
printf("inc_count(): thread %ld, count = %d, unlocking mutex\n",
my_id, count);
pthread_mutex_unlock(&count_mutex);
}
pthread_exit(NULL);
return 0;
}
void* watch_count(void* t)
{
long my_id = (long)t;
printf("Starting watch_count(): thread %ld\n", my_id);
pthread_mutex_lock(&count_mutex);
while (count < COUNT_LIMIT) {
pthread_cond_wait(&count_threshold_cv, &count_mutex);
printf("watch_count(): thread %ld Condition signal received.\n", my_id);
}
count += 125;
printf("watch_count(): thread %ld count now = %d.\n", my_id, count);
pthread_mutex_unlock(&count_mutex);
pthread_exit(NULL);
return 0;
}
int main(int argc, char* argv[])
{
int i, rc;
long t1 = 1, t2 = 2, t3 = 3;
pthread_t threads[3];
pthread_attr_t attr;
pthread_mutex_init(&count_mutex, NULL);
pthread_cond_init(&count_threshold_cv, NULL);
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_create(&threads[0], &attr, watch_count, (void*)t1);
pthread_create(&threads[1], &attr, inc_count, (void*)t2);
pthread_create(&threads[2], &attr, inc_count, (void*)t3);
for (i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
printf("Main(): Waited on %d threads. Done.\n", NUM_THREADS);
pthread_attr_destroy(&attr);
pthread_mutex_destroy(&count_mutex);
pthread_cond_destroy(&count_threshold_cv);
pthread_exit(NULL);
}