1.一个简单的线程
/* hello_multi.c - a multi-threaded hello world program */
#include<stdio.h>
#include<pthread.h>
#define NUM 5
void *print_msg(void *);
void main()
{
pthread_t t1, t2; /* two threads */
pthread_create(&t1, NULL, print_msg, (void *)"hello");
pthread_create(&t2, NULL, print_msg, (void *)"world\n");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
}
void *print_msg(void *m)
{
char *cp = (char *) m;
int i;
for(i=0 ; i<NUM ; i++){
printf("%s", m);
fflush(stdout);
sleep(1);
}
return NULL;
}
运行结果
函数说明
2.线程间的分工合作
多个线程在一个单独的进程中运行,共享全局变量,因此线程间可以通过设置和读取这些全局变量来进行通信但是这样会造成极大的风险。
(1)两个线程、一个计数器twordcount1.c
/* twordcount1.c - threaded word counter for two files. Version 1 */
#include<stdio.h>
#include<pthread.h>
#include<ctype.h>
#include<stdlib.h>
int total_words ;
void main(int ac, char *av[])
{
pthread_t t1, t2; /* two threads */
void *count_words(void *);
if ( ac != 3 ){
printf("usage: %s file1 file2\n", av[0]);
exit(1);
}
total_words = 0;
pthread_create(&t1, NULL, count_words, (void *) av[1]);
pthread_create(&t2, NULL, count_words, (void *) av[2]);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("%5d: total words\n", total_words);
}
void *count_words(void *f)
{
char *filename = (char *) f;
FILE *fp;
int c, prevc = '\0';
if ( (fp = fopen(filename, "r")) != NULL ){
while( ( c = getc(fp)) != EOF ){
if ( !isalnum(c) && isalnum(prevc) )
total_words++;
prevc = c;
}
fclose(fp);
} else
perror(filename);
return NULL;
}
twordcount1和wc的结果并不相同,因为两个程序对于单词结尾的定义并不相同。另外多个线程同时对一个数据读写也会有问题。
(2);两个线程、一个计数器、一个互斥量
线程系统包含了称为互斥锁的变量,它可以使线程间很好的合作,避免对于变量、函数以及资源的访问冲突。
/* twordcount2.c - threaded word counter for two files. */
/* version 2: uses mutex to lock counter */
#include<stdio.h>
#include<pthread.h>
#include<ctype.h>
#include<stdlib.h>
int total_words ; /* the counter and its lock */
pthread_mutex_t counter_lock = PTHREAD_MUTEX_INITIALIZER;
void *count_words(void *);
main(int ac, char *av[])
{
pthread_t t1, t2; /* two threads */
if ( ac != 3 ){
printf("usage: %s file1 file2\n", av[0]);
exit(1);
}
total_words = 0;
pthread_create(&t1, NULL, count_words, (void *) av[1]);
pthread_create(&t2, NULL, count_words, (void *) av[2]);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("%5d: total words\n", total_words);
}
void *count_words(void *f)
{
char *filename = (char *) f;
FILE *fp;
int c, prevc = '\0';
if ( (fp = fopen(filename, "r")) != NULL ){
while( ( c = getc(fp)) != EOF ){
if ( !isalnum(c) && isalnum(prevc) ){
pthread_mutex_lock(&counter_lock);
total_words++;
pthread_mutex_unlock(&counter_lock);
}
prevc = c;
}
fclose(fp);
} else
perror(filename);
return NULL;
}
(3)两个线程、两个计数器、向线程传递多个参数
这个版本的字数统计为每个线程设置了自己的计数器,从而避免了对于互斥量的使用。当线程返回之后,再将这两个计数器的值加起来得到最后的结果。
/* twordcount3.c - threaded word counter for two files.
* - Version 3: one counter per file
*/
#include<stdio.h>
#include<pthread.h>
#include<ctype.h>
#include<stdlib.h>
struct arg_set { /* two values in one arg */
char *fname; /* file to examine */
int count; /* number of words */
};
void main(int ac, char *av[])
{
pthread_t t1, t2; /* two threads */
struct arg_set args1, args2; /* two argsets */
void *count_words(void *);
if ( ac != 3 ){
printf("usage: %s file1 file2\n", av[0]);
exit(1);
}
args1.fname = av[1];
args1.count = 0;
pthread_create(&t1, NULL, count_words, (void *) &args1);
args2.fname = av[2];
args2.count = 0;
pthread_create(&t2, NULL, count_words, (void *) &args2);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("%5d: %s\n", args1.count, av[1]);
printf("%5d: %s\n", args2.count, av[2]);
printf("%5d: total words\n", args1.count+args2.count);
}
void *count_words(void *a)
{
struct arg_set *args = a; /* cast arg back to correct type */
FILE *fp;
int c, prevc = '\0';
if ( (fp = fopen(args->fname, "r")) != NULL ){
while( ( c = getc(fp)) != EOF ){
if ( !isalnum(c) && isalnum(prevc) )
args->count++;
prevc = c;
}
fclose(fp);
} else
perror(args->fname);
return NULL;
}
进程与线程有根本上的不同,每个进程有其独立的数据空间、文件描述符以及进程ID。而线程共享一个数据空间、文件描述符以及进程ID。
3.线程间互通消息(条件变量)
/* twordcount4.c - threaded word counter for two files.
* - Version 4: condition variable allows counter
* functions to report results early
*/
#include<stdio.h>
#include<pthread.h>
#include<ctype.h>
#include<stdlib.h>
struct arg_set { /* two values in one arg*/
char *fname; /* file to examine */
int count; /* number of words */
};
struct arg_set *mailbox = NULL;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t flag = PTHREAD_COND_INITIALIZER;
void *count_words(void *);
void main(int ac, char *av[])
{
pthread_t t1, t2; /* two threads */
struct arg_set args1, args2; /* two argsets */
int reports_in = 0;
int total_words = 0;
if ( ac != 3 ){
printf("usage: %s file1 file2\n", av[0]);
exit(1);
}
pthread_mutex_lock(&lock); /* lock the report box now */
args1.fname = av[1];
args1.count = 0;
pthread_create(&t1, NULL, count_words, (void *) &args1);
args2.fname = av[2];
args2.count = 0;
pthread_create(&t2, NULL, count_words, (void *) &args2);
while( reports_in < 2 ){
printf("MAIN: waiting for flag to go up\n");
pthread_cond_wait(&flag, &lock); /* wait for notify */
printf("MAIN: Wow! flag was raised, I have the lock\n");
printf("%7d: %s\n", mailbox->count, mailbox->fname);
total_words += mailbox->count;
if ( mailbox == &args1)
pthread_join(t1,NULL);
if ( mailbox == &args2)
pthread_join(t2,NULL);
mailbox = NULL;
// pthread_cond_signal(&flag); /* announce state change */
reports_in++;
}
printf("%7d: total words\n", total_words);
}
void *count_words(void *a)
{
struct arg_set *args = a; /* cast arg back to correct type */
FILE *fp;
int c, prevc = '\0';
if ( (fp = fopen(args->fname, "r")) != NULL ){
while( ( c = getc(fp)) != EOF ){
if ( !isalnum(c) && isalnum(prevc) )
args->count++;
prevc = c;
}
fclose(fp);
} else
perror(args->fname);
printf("COUNT: waiting to get lock\n");
pthread_mutex_lock(&lock); /* get the mailbox */
printf("COUNT: have lock, storing data\n");
//if ( mailbox != NULL ){
// printf("COUNT: oops..mailbox not empty. wait for signal\n");
// pthread_cond_wait(&flag,&lock);
//}
mailbox = args; /* put ptr to our args there */
printf("COUNT: raising flag\n");
pthread_cond_signal(&flag); /* raise the flag */
printf("COUNT: unlocking box\n");
pthread_mutex_unlock(&lock); /* release the mailbox */
return NULL;
}
4.防止僵尸线程:创建独立线程
本章中所有的线程中都用到了pthread_join函数来等待线程返回,主线程阻塞等待子线程结束,然后回收子线程资源。如果忘记使用pthread_join函数,线程占用的资源将无法回收。
使用pthread_join函数:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
void *thread_function(void *arg)
{
int i;
for ( i=0; i<8; i++)
{
printf("Thread working...! %d \n",i);
sleep(1);
}
return NULL;
}
int main(void)
{
pthread_t mythread;
if ( pthread_create( &mythread, NULL, thread_function, NULL) )
{
printf("error creating thread.");
abort();
}
if ( pthread_join ( mythread, NULL ) )
{
printf("error join thread.");
abort();
}
printf("thread done! \n");
exit(0);
}
可以创建独立的线程,当函数执行完之后自动释放所有的资源,不用返回。
(1)在线程创建时将其属性设为分离状态(detached)
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
void *thread_function(void *arg)
{
int i;
for ( i=0; i<8; i++)
{
printf("Thread working...! %d \n",i);
sleep(1);
}
return NULL;
}
int main(void)
{
pthread_t mythread;
pthread_attr_t attr_detached;
pthread_attr_init(&attr_detached);
pthread_attr_setdetachstate(&attr_detached, PTHREAD_CREATE_DETACHED);
if ( pthread_create( &mythread, &attr_detached, thread_function, NULL) )
{
printf("error creating thread.");
abort();
}
pthread_detach(mythread);
sleep(2);
printf("thread done! \n");
pthread_exit(0);
}
(2)在线程创建后将其属性设为分离的,使用pthread_detach函数
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
void *thread_function(void *arg)
{
int i;
for ( i=0; i<8; i++)
{
printf("Thread working...! %d \n",i);
sleep(1);
}
return NULL;
}
int main(void)
{
pthread_t mythread;
if ( pthread_create( &mythread, NULL, thread_function, NULL) )
{
printf("error creating thread.");
abort();
}
pthread_detach(mythread);
sleep(2);
printf("thread done! \n");
pthread_exit(0);
}
5、线程和动画
(1)多线程版的bounce1d.c
在第七章中使用了定时器来控制动画。现在使用一个线程来处理键盘输入,另一个线程来处理动画的移动。修改后的程序仍然使用全局变量表示msg的状态。
/* tbounce1d.c: controlled animation using two threads
* note one thread handles animation
* other thread handles keyboard input
* compile cc tbounce1d.c -lcurses -lpthread -o tbounce1d
*/
#include<stdio.h>
#include<curses.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/time.h>
/* shared variables both threads use. These need a mutex. */
#define MESSAGE " hello "
int row; /* current row */
int col; /* current column */
int dir; /* where we are going */
int delay; /* delay between moves */
void *moving_msg();
void main()
{
int ndelay; /* new delay */
int c; /* user input */
pthread_t msg_thread; /* a thread */
initscr(); /* init curses and tty */
crmode();
noecho();
clear();
row = 10; /* start here */
col = 0;
dir = 1; /* add 1 to row number */
delay = 200; /* 200ms = 0.2 seconds */
if ( pthread_create(&msg_thread,NULL,moving_msg,MESSAGE) ){
fprintf(stderr,"error creating thread");
endwin();
exit(0);
}
while(1) {
ndelay = 0;
c = getch();
if ( c == 'Q' ) break;
if ( c == ' ' ) dir = -dir;
if ( c == 'f' && delay > 2 ) ndelay = delay/2;
if ( c == 's' ) ndelay = delay * 2 ;
if ( ndelay > 0 )
delay = ndelay ;
}
pthread_cancel(msg_thread);
endwin();
}
void *moving_msg(char *msg)
{
while( 1 ) {
usleep(delay*1000); /* sleep a while */
move( row, col ); /* set cursor position */
addstr( msg ); /* redo message */
refresh(); /* and show it */
/* move to next column and check for bouncing */
col += dir; /* move to new column */
if ( col <= 0 && dir == -1 )
dir = 1;
else if ( col+strlen(msg) >= COLS && dir == 1 )
dir = -1;
}
}
int set_ticker( int n_msecs )
{
struct itimerval new_timeset;
long n_sec, n_usecs;
n_sec = n_msecs / 1000 ; /* int part */
n_usecs = ( n_msecs % 1000 ) * 1000L ; /* remainder */
new_timeset.it_interval.tv_sec = n_sec; /* set reload */
new_timeset.it_interval.tv_usec = n_usecs; /* new ticker value */
new_timeset.it_value.tv_sec = n_sec ; /* store this */
new_timeset.it_value.tv_usec = n_usecs ; /* and this */
return setitimer(ITIMER_REAL, &new_timeset, NULL);
}
(2)基于多线程机制的多重动画:tanimate.c
/* tanimate.c: animate several strings using threads, curses, usleep()
*
* bigidea one thread for each animated string
* one thread for keyboard control
* shared variables for communication
* compile cc tanimate.c -lcurses -lpthread -o tanimate
* to do needs locks for shared variables
* nice to put screen handling in its own thread
*/
#include<stdio.h>
#include<curses.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#define MAXMSG 10 /* limit to number of strings */
#define TUNIT 20000 /* timeunits in microseconds */
struct propset {
char *str; /* the message */
int row; /* the row */
int delay; /* delay in time units */
int dir; /* +1 or -1 */
};
pthread_mutex_t mx = PTHREAD_MUTEX_INITIALIZER;
void *animate(void *arg);
int setup(int nstrings, char *strings[], struct propset props[]);
int main(int ac, char *av[])
{
int c; /* user input */
pthread_t thrds[MAXMSG]; /* the threads */
struct propset props[MAXMSG]; /* properties of string */
int num_msg ; /* number of strings */
int i;
if ( ac == 1 ){
printf("usage: tanimate string ..\n");
exit(1);
}
num_msg = setup(ac-1,av+1,props);
/* create all the threads */
for(i=0 ; i<num_msg; i++)
if ( pthread_create(&thrds[i], NULL, animate, &props[i])){
fprintf(stderr,"error creating thread");
endwin();
exit(0);
}
/* process user input */
while(1) {
c = getch();
if ( c == 'Q' ) break;
if ( c == ' ' )
for(i=0;i<num_msg;i++)
props[i].dir = -props[i].dir;
if ( c >= '0' && c <= '9' ){
i = c - '0';
if ( i < num_msg )
props[i].dir = -props[i].dir;
}
}
/* cancel all the threads */
pthread_mutex_lock(&mx);
for (i=0; i<num_msg; i++ )
pthread_cancel(thrds[i]);
endwin();
return 0;
}
int setup(int nstrings, char *strings[], struct propset props[])
{
int num_msg = ( nstrings > MAXMSG ? MAXMSG : nstrings );
int i;
/* assign rows and velocities to each string */
srand(getpid());
for(i=0 ; i<num_msg; i++){
props[i].str = strings[i]; /* the message */
props[i].row = i; /* the row */
props[i].delay = 1+(rand()%15); /* a speed */
props[i].dir = ((rand()%2)?1:-1); /* +1 or -1 */
}
/* set up curses */
initscr();
crmode();
noecho();
clear();
mvprintw(LINES-1,0,"'Q' to quit, '0'..'%d' to bounce",num_msg-1);
return num_msg;
}
/* the code that runs in each thread */
void *animate(void *arg)
{
struct propset *info = arg; /* point to info block */
int len = strlen(info->str)+2; /* +2 for padding */
int col = rand()%(COLS-len-3); /* space for padding */
while( 1 )
{
usleep(info->delay*TUNIT);
pthread_mutex_lock(&mx); /* only one thread */
move( info->row, col ); /* can call curses */
addch(' '); /* at a the same time */
addstr( info->str ); /* Since I doubt it is */
addch(' '); /* reentrant */
move(LINES-1,COLS-1); /* park cursor */
refresh(); /* and show it */
pthread_mutex_unlock(&mx); /* done with curses */
/* move item to next column and check for bouncing */
col += info->dir;
if ( col <= 0 && info->dir == -1 )
info->dir = 1;
else if ( col+len >= COLS && info->dir == 1 )
info->dir = -1;
}
}
运行结果:
上述代码分为三段:初始化、控制移动消息的函数和一个读取和处理用户输入的循环。用户输入循环在初始线程中运行,而控制动画的函数却是运行在好几个线程中。
方向变量是控制动画的线程和用户输入线程的共享变量,因此需要互斥量来保护。屏幕也被所有线程共享,因此使用互斥量来防止这些函数的冲突。
6.小结